Tue, 02 Jul 2019 12:10:23 +0200
Adjusted more strings for new compiler warnings. There should now be no buffer overflows in the app. Changed the date display on the mainscreen.
/** * @file task_sdcard.c * @brief SD/MMC driver. This driver is for a slot for the user on * the front panel. It will detect an inserted card and then * mount it. It also detects the removal of the card and then * unmounts it. Be carefull, only do this in the main menu. * Also, brew logging is handled here and finished brewlogs * are copied to the SD card if it is mounted. * Recipes to import must go into the /sdcard/recipe folder * and have extension .xml and the contents be a beerxml file. * Backup and restore is also done to this card. */ #include "vfs_fat_internal.h" #include "driver/sdmmc_host.h" #include "driver/sdspi_host.h" #include "sdmmc_cmd.h" #include "diskio_impl.h" #include "diskio_sdmmc.h" #include "config.h" SDCARD_State *sdcard_state; ///< SD card status JSON_log *json_log; ///< JSON log array EventGroupHandle_t xEventGroupSDcard; ///< SD card events. static const char *TAG = "task_sdcard"; static sdmmc_card_t* s_card = NULL; static uint8_t s_pdrv = 0; static char * s_base_path = NULL; BYTE pdrv = 0xFF; #define SDCARD_HOST_SLOT VSPI_HOST ///< HSPI_HOST is used by the TFT #define SDCARD_PIN_NUM_MISO 2 ///< MISO pin #define SDCARD_PIN_NUM_MOSI 15 ///< MOSI pin #define SDCARD_PIN_NUM_CLK 14 ///< CLOCK #define SDCARD_PIN_NUM_CS 13 ///< Chip select pin #define SDCARD_DMA_CHANNEL 2 ///< Channel 1 is used by the TFT const int TASK_SDCARD_LOG_CLEAN = BIT0; ///< Clean spiffs logfile directory const int TASK_SDCARD_LOG_CLOSE = BIT1; ///< Close the logfile. extern time_t now; extern struct tm timeinfo; extern uint32_t TimeBrewing; void log_begin(time_t t) { struct tm timeinfo; /* * If there is an open logfile from an crashed brew, open that one. */ if (! strlen(sdcard_state->logfile) && strlen(runtime.Logfile)) { sprintf(sdcard_state->logfile, "%s", runtime.Logfile); ESP_LOGI(TAG, "Resumed %s", sdcard_state->logfile); } else { localtime_r(&t, &timeinfo); if (timeinfo.tm_year > (2016 - 1900)) { // Valid time, construct the filename. strftime(sdcard_state->logfile, sizeof(sdcard_state->logfile), "br%y%m%d%H%M", &timeinfo); } else { sprintf(sdcard_state->logfile, "brewlog"); } ESP_LOGI(TAG, "Create %s", sdcard_state->logfile); sprintf(runtime.Logfile, "%s", sdcard_state->logfile); } } void log_close(void) { xEventGroupSetBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLOSE); } void log_clean(void) { xEventGroupSetBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLEAN); } void log_json(void) { char filename[64], strftime_buf[64]; FILE *f; bool addcomma = true; if (strlen(sdcard_state->logfile)) { sprintf(filename, "/spiffs/log/%s.json", sdcard_state->logfile); f = fopen(filename, "r"); if (f == NULL) { // Create the file and add the header f = fopen(filename, "a"); if (f) { strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo); fprintf(f, "{\n"); fprintf(f, " \"brew\": [{\n"); fprintf(f, " \"Recipe\":\"%s %s\",\n", recipe.Code, recipe.Name); fprintf(f, " \"Date\":\"%s\",\n", strftime_buf); fprintf(f, " \"brewdata\":[\n"); addcomma = false; fclose(f); } else { ESP_LOGE(TAG, "Failed to create %s error %d", filename, errno); return; } } else { fclose(f); // Was open for reading. } f = fopen(filename, "a"); if (f) { if (addcomma) { fprintf(f, ",\n"); } addcomma = true; fprintf(f, " {\"MLT_sp\":\"%.3f\",\"MLT_pv\":\"%.3f\",\"MLT_pwm\":\"%d\",\"MLT_tr\":\"%d\",\"Pump\":\"%d\",", json_log->mlt_sp, json_log->mlt_pv, json_log->mlt_power, json_log->mlt_tempreached, json_log->pump_run); if (json_log->hlt_sp > 0.0) { fprintf(f, "\"HLT_sp\":\"%.3f\",\"HLT_pv\":\"%.3f\",\"HLT_pwm\":\"%d\",", json_log->hlt_sp, json_log->hlt_pv, json_log->hlt_power); } fprintf(f, "\"Label\":\"%s\"}", json_log->time); fclose(f); } } } void log_annotation(int annotation_type, char *label) { char filename[64]; char bordercolor[9], color[9], pos[8]; FILE *f; bool addcomma = true; int Hour = (TimeBrewing / 3600); int Minute = ((TimeBrewing % 3600) / 60); if (strlen(sdcard_state->logfile)) { sprintf(filename, "/spiffs/log/%s.anno", sdcard_state->logfile); switch (annotation_type) { case ANNOTATION_STAGE: snprintf(bordercolor, 8, "#8942f4"); snprintf(color, 8, "#00215b"); snprintf(pos, 7, "bottom"); break; case ANNOTATION_EVENT: snprintf(bordercolor, 8, "#42f445"); snprintf(color, 8, "#00215b"); snprintf(pos, 7, "top"); break; case ANNOTATION_SYSTEM: snprintf(bordercolor, 8, "black"); snprintf(color, 8, "red"); snprintf(pos, 7, "center"); break; } // Check if the file exists to see if we need to insert a comma. f = fopen(filename, "r"); if (f == NULL) { addcomma = false; } else { fclose(f); } snprintf(json_log->time, 11, "%02d:%02d", Hour, Minute); f = fopen(filename, "a"); if (f) { if (addcomma) { fprintf(f, ",\n"); } addcomma = true; fprintf(f, "{\"type\":\"line\",\"mode\":\"vertical\",\"scaleID\":\"x-axis-0\",\"value\":\"%s\",\"borderColor\":\"%s\",\"borderWidth\":2,\"label\":{\"backgroundColor\":\"%s\",\"content\":\"%s\",\"position\":\"%s\",\"enabled\":true}}", json_log->time, bordercolor, color, label, pos); fclose(f); } } } /** * @brief This is a local modified version of the esp_vfs_fat_sdmmc_mount() function in * the FreeRTOS components library. It is here so we can better handle errors * for our application. * @param base_path The mount path * @param host_config SPI host configuration * @param slot_config Slot configuration * @return Error condition. */ esp_err_t my_vfs_fat_sdmmc_init(const char* base_path, const sdmmc_host_t* host_config, const void* slot_config) { if (s_card != NULL) { return ESP_ERR_INVALID_STATE; } // connect SDMMC driver to FATFS pdrv = 0xFF; if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == 0xFF) { ESP_LOGI(TAG, "the maximum count of volumes is already mounted"); return ESP_ERR_NO_MEM; } s_base_path = strdup(base_path); if (!s_base_path) { ESP_LOGI(TAG, "could not copy base_path"); return ESP_ERR_NO_MEM; } esp_err_t err = ESP_OK; s_card = malloc(sizeof(sdmmc_card_t)); if (s_card == NULL) { err = ESP_ERR_NO_MEM; goto fail; } err = (*host_config->init)(); if (err != ESP_OK) { ESP_LOGI(TAG, "host init returned rc=0x%x", err); goto fail; } // configure SD slot if (host_config->flags == SDMMC_HOST_FLAG_SPI) { err = sdspi_host_init_slot(host_config->slot, (const sdspi_slot_config_t*) slot_config); } else { err = sdmmc_host_init_slot(host_config->slot, (const sdmmc_slot_config_t*) slot_config); } if (err != ESP_OK) { ESP_LOGI(TAG, "slot_config returned rc=0x%x", err); goto fail; } return ESP_OK; fail: host_config->deinit(); free(s_card); s_card = NULL; return err; } /** * @brief Mount SD card. * @param base_path The mountpoint * @param host_config SPI host configuration * @param slot_config Slot configuration * @param mount_config Mount configuration * @param[out] out_card Card information * @return Error condition */ esp_err_t my_esp_vfs_fat_sdmmc_mount(const char* base_path, const sdmmc_host_t* host_config, const void* slot_config, const esp_vfs_fat_mount_config_t* mount_config, sdmmc_card_t** out_card) { FATFS* fs = NULL; esp_err_t err = ESP_OK; if (s_card == NULL) { return ESP_ERR_INVALID_STATE; } // probe and initialize card err = sdmmc_card_init(host_config, s_card); if (err != ESP_OK) { if (err != ESP_ERR_INVALID_RESPONSE) { // No card present, do not log ESP_LOGI(TAG, "sdmmc_card_init failed 0x(%x)", err); } goto fail; } if (out_card != NULL) { *out_card = s_card; } ff_diskio_register_sdmmc(pdrv, s_card); s_pdrv = pdrv; ESP_LOGD(TAG, "using pdrv=%i", pdrv); char drv[3] = {(char)('0' + pdrv), ':', 0}; // connect FATFS to VFS err = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs); if (err == ESP_ERR_INVALID_STATE) { // it's okay, already registered with VFS } else if (err != ESP_OK) { ESP_LOGI(TAG, "esp_vfs_fat_register failed 0x(%x)", err); goto fail; } // Try to mount partition FRESULT res = f_mount(fs, drv, 1); if (res != FR_OK) { err = ESP_FAIL; ESP_LOGD(TAG, "f_mount failed (%d)", res); goto fail; } return ESP_OK; fail: if (fs) { f_mount(NULL, drv, 0); } esp_vfs_fat_unregister_path(base_path); ff_diskio_unregister(pdrv); return err; } /** * @brief Unmount a mounted SD card, * @return Error condition */ esp_err_t my_esp_vfs_fat_sdmmc_unmount(void) { if (s_card == NULL) { return ESP_ERR_INVALID_STATE; } // unmount char drv[3] = {(char)('0' + s_pdrv), ':', 0}; f_mount(0, drv, 0); return ESP_OK; } int FileCopy(char *ff, char *tf) { FILE *f, *t; uint8_t buf[512]; size_t bytes; f = fopen(ff, "r"); if (f == NULL) { ESP_LOGE(TAG, "FileCopy cannot open %s for read, error %d", ff, errno); return 1; } t = fopen(tf, "w+"); if (t == NULL) { ESP_LOGE(TAG, "FileCopy cannot open %s for create/write, error %d", tf, errno); fclose(f); return 1; } while ((bytes = fread(&buf, 1, 512, f))) { fwrite(&buf, 1, bytes, t); vTaskDelay(10 / portTICK_PERIOD_MS); } fclose(f); fclose(t); return 0; } void task_sdcard(void *pvParameter) { sdmmc_card_t* card; esp_err_t ret; EventBits_t uxBits; char filename[64]; sdcard_state = malloc(sizeof(SDCARD_State)); sdcard_state->host_ok = false; sdcard_state->card_present = false; sdcard_state->logfile[0] = '\0'; json_log = malloc(sizeof(JSON_log)); ESP_LOGI(TAG, "Starting SD card"); sdmmc_host_t host = SDSPI_HOST_DEFAULT(); host.slot = SDCARD_HOST_SLOT; // HSPI_HOST is in use by the TFT. sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT(); slot_config.gpio_miso = SDCARD_PIN_NUM_MISO; slot_config.gpio_mosi = SDCARD_PIN_NUM_MOSI; slot_config.gpio_sck = SDCARD_PIN_NUM_CLK; slot_config.gpio_cs = SDCARD_PIN_NUM_CS; slot_config.dma_channel = SDCARD_DMA_CHANNEL; // This initializes the slot without card detect (CD) and write protect (WP) signals. // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. /* * No errors from the sdmmc_cmd driver. */ esp_log_level_set("sdmmc_cmd", ESP_LOG_NONE); /* * Options for mounting the filesystem. * If format_if_mount_failed is set to true, SD card will be partitioned and * formatted in case when mounting fails. */ esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = false, .max_files = 5, .allocation_unit_size = 16 * 1024 }; ret = my_vfs_fat_sdmmc_init("/sdcard", &host, &slot_config); if (ret == ESP_OK) { ESP_LOGI(TAG, "SPI card interface ready"); sdcard_state->host_ok = true; } else { ESP_LOGE(TAG, "SPI card interface failed, abort task"); vTaskDelete(NULL); return; } xEventGroupSDcard = xEventGroupCreate(); /* * Task loop, continues check of the inserted cards. */ while (1) { if (sdcard_state->card_present == false) { /* * If the card is not mounted, try it. */ ret = my_esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); if (ret == ESP_OK) { ESP_LOGI(TAG, "SD card mounted on /sdcard"); sdcard_state->card_present = true; DIR* dir = opendir("/sdcard/w/log"); if (dir == NULL) { ret = mkdir("/sdcard/w/log", 0755); } else { closedir(dir); } } } else { /* * Check if the mounted card is still in the slot. */ DIR* dir = opendir("/sdcard/w/log"); if (dir == NULL) { ESP_LOGI(TAG, "SD card missing, unmount"); my_esp_vfs_fat_sdmmc_unmount(); sdcard_state->card_present = false; } else { closedir(dir); } } uxBits = xEventGroupWaitBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLEAN | TASK_SDCARD_LOG_CLOSE, pdFALSE, pdFALSE, 1000 / portTICK_PERIOD_MS); if (uxBits & TASK_SDCARD_LOG_CLEAN) { DIR *dir = opendir("/spiffs/log"); char lf[32]; if (dir != NULL) { struct dirent *de = readdir(dir); while (de) { sprintf(lf, "/spiffs/log/"); strncat(lf, de->d_name, 31 - strlen(lf)); if (unlink(lf) == ESP_OK) { ESP_LOGI(TAG, "Removed old %s", lf); } de = readdir(dir); vTaskDelay(2 / portTICK_PERIOD_MS); } closedir(dir); } xEventGroupClearBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLEAN); } if (uxBits & TASK_SDCARD_LOG_CLOSE) { // Close the logfile. if (strlen(sdcard_state->logfile) && (sdcard_state->card_present == true)) { ESP_LOGI(TAG, "Closing logfile"); char destname[64]; sprintf(destname, "/sdcard/w/log"); int rc = mkdir(destname, 0755); if (rc && (errno != EEXIST)) { ESP_LOGE(TAG, "Cannot create %s error %d", destname, errno); } else { sprintf(filename, "/spiffs/log/%s.json", sdcard_state->logfile); // First close the JSON data records FILE *f = fopen(filename, "a+"); if (f) { fprintf(f, " ],\n"); // End of brewdata fprintf(f, " \"annotations\":[\n"); // Insert annotation records sprintf(destname, "/spiffs/log/%s.anno", sdcard_state->logfile); FILE *a = fopen(destname, "r"); char buf[256]; if (a) { while(true) { if (fgets(buf, sizeof(buf), a)) { fprintf(f, "%s", buf); } else { break; } } fclose(a); unlink(destname); } fprintf(f, " ]\n"); // End of annotations fprintf(f, " }]\n"); // End of brew fprintf(f, "}\n"); fclose(f); } sprintf(destname, "/sdcard/w/log/%s.json", sdcard_state->logfile); if (FileCopy(filename, destname) == 0) { ESP_LOGI(TAG, "JSON file copied to %s", destname); unlink(filename); } } } sdcard_state->logfile[0] = '\0'; // Clear logfile name runtime.Logfile[0] = '\0'; xEventGroupClearBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLOSE); } } }