diff -r 000000000000 -r b74b0e4902c3 main/task_sdcard.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/task_sdcard.c Sat Oct 20 13:23:15 2018 +0200 @@ -0,0 +1,588 @@ +/** + * @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. But, it cannot detect the removal of a card!! + * 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. + */ + + +#include "vfs_fat_internal.h" +#include "driver/sdmmc_host.h" +#include "driver/sdspi_host.h" +#include "sdmmc_cmd.h" +#include "diskio.h" + +#include "config.h" + + + +SDCARD_State * sdcard_state; +JSON_log * json_log; +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 +#define SDCARD_PIN_NUM_MOSI 15 +#define SDCARD_PIN_NUM_CLK 14 +#define SDCARD_PIN_NUM_CS 13 +#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; + + + +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[32], 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\",\n", 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); + printf("{\"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) { + printf("\"HLT_sp\":\"%.3f\",\"HLT_pv\":\"%.3f\",\"HLT_pwm\":\"%d\",", json_log->hlt_sp, json_log->hlt_pv, json_log->hlt_power); + } + printf("\"Label\":\"%s\"}\n", json_log->time); + fclose(f); + } + } +} + + + +void log_annotation(int annotation_type, char *label) +{ + char filename[32]; + char bordercolor[9], color[9], pos[8]; + FILE *f; + bool addcomma = true; + + 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); + } + + 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); + printf( "{\"type\":\"line\",\"mode\":\"vertical\",\"scaleID\":\"x-axis-0\",\"value\":\"%s\",\"borderColor\":\"%s\",\"borderWidth\":2,\"label\":{\"backgroundColor\":\"%s\",\"content\":\"%s\",\"position\":\"%s\",\"enabled\":true}}\n", json_log->time, bordercolor, color, label, pos); + } + } +} + + + +/* + * 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. + */ +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; +} + + + +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; +} + + + +esp_err_t my_esp_vfs_fat_sdmmc_unmount() +{ + 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 SyncDirs(char *fromdir, char *todir) +{ + char ff[64], tf[64]; + struct stat fs, ts; + int rc; + + ESP_LOGI(TAG, "SyncDirs(%s, %s)", fromdir, todir); + + DIR *dir = opendir(fromdir); + struct dirent* de = readdir(dir); + while (de) { + if (de->d_type == DT_REG) { + sprintf(ff, "%s/%s", fromdir, de->d_name); + if (stat(ff, &fs) == ESP_OK) { + + sprintf(tf, "%s/%s", todir, de->d_name); + if (stat(tf, &ts) != ESP_OK) { + ts.st_size = 0; + } + + if (fs.st_size && (fs.st_size != ts.st_size)) { + rc = FileCopy(ff, tf); + ESP_LOGI(TAG, "Copy %s to %s, %ld bytes, rc=%d", ff, todir, fs.st_size, rc); + } + } + } + de = readdir(dir); + vTaskDelay(50 / portTICK_PERIOD_MS); + } + closedir(dir); + + /* + * Now see if we need to remove files. + */ + dir = opendir(todir); + de = readdir(dir); + while (de) { + sprintf(tf, "%s/%s", todir, de->d_name); + sprintf(ff, "%s/%s", fromdir, de->d_name); + + if (stat(ff, &fs) != ESP_OK) { + if (unlink(tf) == ESP_OK) { + ESP_LOGI(TAG, "Removed %s", tf); + } + } + + de = readdir(dir); + vTaskDelay(50 / portTICK_PERIOD_MS); + } + closedir(dir); +} + + + +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); + printf("Dir created ret=%d\n", ret); + } else { + closedir(dir); + } + + + SyncDirs("/sdcard/w", "/spiffs/w"); + SyncDirs("/sdcard/w/js", "/spiffs/w/js"); + SyncDirs("/sdcard/w/css", "/spiffs/w/css"); +// SyncDirs("/sdcard/w/js/modl" , "/spiffs/w/js/modl"); // +// SyncDirs("/sdcard/w/js/utils", "/spiffs/w/js/utils"); +// SyncDirs("/sdcard/w/js/zlib", "/spiffs/w/js/zlib"); +// SyncDirs("/sdcard/w/core", "/spiffs/w/core"); +// SyncDirs("/sdcard/w/core/input", "/spiffs/w/core/input"); +// SyncDirs("/sdcard/w/core/util", "/spiffs/w/core/util"); +// SyncDirs("/sdcard/w/app", "/spiffs/w/app"); +// SyncDirs("/sdcard/w/app/images", "/spiffs/w/app/images"); +// SyncDirs("/sdcard/w/app/locale", "/spiffs/w/app/locale"); +// SyncDirs("/sdcard/w/app/sounds", "/spiffs/w/app/sounds"); // + SyncDirs("/sdcard/w/app/styles", "/spiffs/w/app/styles"); +// SyncDirs("/sdcard/fonts", "/spiffs/fonts"); // + + } + } 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/%s", de->d_name); + 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); + } + } +} + +