Sun, 14 Jan 2024 11:19:19 +0100
Some kicad upgrade, no project changes.
/** * @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. int card_handle = -1; static const char *TAG = "task_sdcard"; static sdmmc_card_t* card = NULL; static uint8_t pdrv = FF_DRV_NOT_USED; char drv[3] = {'0', ':', 0}; #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; extern bool System_TimeOk; void log_msg(const char *tag, const char *format, ...) { char *outstr, logfn[64], stamp[32]; va_list va_ptr; FILE *fp; outstr = calloc(1024, sizeof(char)); va_start(va_ptr, format); vsnprintf(outstr, 1023, format, va_ptr); ESP_LOGI(tag, "%s", outstr); if (System_TimeOk && sdcard_state->card_present == true) { snprintf(logfn, 64, "/sdcard/w/log/sys%04d%02d%02d.log", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday); snprintf(stamp, 31, "%02d-%02d %02d:%02d:%02d", timeinfo.tm_mday, timeinfo.tm_mon+1, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); fp = fopen(logfn, "a+"); if (fp) { fprintf(fp, "%s %s: %s\n", stamp, tag, outstr); fclose(fp); } } va_end(va_ptr); free(outstr); } 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); log_msg(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"); } log_msg(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, "{\"brew\":[{\"Recipe\":\"%s %s\",\"Date\":\"%s\",\"brewdata\":[\n", recipe.Code, recipe.Name, strftime_buf); 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); } } } static esp_err_t mount_prepare_mem(const char *base_path, BYTE *out_pdrv, char **out_dup_path) { esp_err_t err = ESP_OK; char* dup_path = NULL; // connect SDMMC driver to FATFS BYTE pdrv = FF_DRV_NOT_USED; if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == FF_DRV_NOT_USED) { ESP_LOGD(TAG, "the maximum count of volumes is already mounted"); return ESP_ERR_NO_MEM; } // not using ff_memalloc here, as allocation in internal RAM is preferred card = (sdmmc_card_t*)malloc(sizeof(sdmmc_card_t)); if (card == NULL) { ESP_LOGD(TAG, "could not malloc sdmmc_card_t"); err = ESP_ERR_NO_MEM; goto cleanup; } dup_path = strdup(base_path); if (!dup_path){ ESP_LOGD(TAG, "could not copy base_path"); err = ESP_ERR_NO_MEM; goto cleanup; } *out_pdrv = pdrv; *out_dup_path = dup_path; return ESP_OK; cleanup: free(card); card = NULL; free(dup_path); return err; } static esp_err_t my_init_sdspi_host(int slot, const void *slot_config, int *out_slot) { esp_err_t err = sdspi_host_init_device((const sdspi_device_config_t*)slot_config, out_slot); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to attach sdspi device onto an SPI bus (rc=0x%x), please initialize the \ bus first and check the device parameters.", err); } return err; } /** * @brief This is a local modified version of the esp_vfs_fat_sdspi_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_sdspi_init(const char* base_path, const sdmmc_host_t* host_config, const void* slot_config) { esp_err_t err; char* dup_path = NULL; if (card != NULL) { ESP_LOGE(TAG, "card not NULL"); return ESP_ERR_INVALID_STATE; } err = mount_prepare_mem(base_path, &pdrv, &dup_path); if (err != ESP_OK) { ESP_LOGE(TAG, "mount_prepare_mem 0x%x", err); return err; } //the init() function is usually empty, doesn't require any deinit to revert it err = (*host_config->init)(); if (err != ESP_OK) { ESP_LOGE(TAG, "host_config->init() 0x%x", err); goto fail; } // configure SD host err = my_init_sdspi_host(host_config->slot, slot_config, &card_handle); if (err != ESP_OK) { ESP_LOGE(TAG, "my_init_sdspi_host() 0x%x", err); goto fail; } return ESP_OK; fail: host_config->deinit(); free(card); 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_sdspi_mount(const char* base_path, const sdmmc_host_t* host_config_input, const sdspi_device_config_t* slot_config, const esp_vfs_fat_mount_config_t* mount_config, sdmmc_card_t** out_card) { const sdmmc_host_t* host_config = host_config_input; FATFS* fs = NULL; esp_err_t err = ESP_OK; if (card == NULL) { ESP_LOGE(TAG, "card NULL"); return ESP_ERR_INVALID_STATE; } /* * The `slot` argument inside host_config should be replaced by the SD SPI handled returned * above. But the input pointer is const, so create a new variable. */ sdmmc_host_t new_config; if (card_handle != host_config->slot) { new_config = *host_config_input; host_config = &new_config; new_config.slot = card_handle; } // probe and initialize card err = sdmmc_card_init(host_config, 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 = card; } /* * mount to vfs fat */ ff_diskio_register_sdmmc(pdrv, card); ESP_LOGD(TAG, "using pdrv=%i", pdrv); drv[0] = (char)('0' + pdrv); // 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 // See: esp-idf/components/fatfs/src/ff.h 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_sdspi_unmount(void) { if (card == NULL) { return ESP_ERR_INVALID_STATE; } // unmount 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_LOGW(TAG, "FileCopy cannot open %s for read, error %d", ff, errno); return 1; } t = fopen(tf, "w+"); if (t == NULL) { ESP_LOGW(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, "Start SD card"); sdmmc_host_t host = SDSPI_HOST_DEFAULT(); host.slot = SDCARD_HOST_SLOT; // HSPI_HOST is in use by the TFT. spi_bus_config_t bus_cfg = { .mosi_io_num = SDCARD_PIN_NUM_MOSI, .miso_io_num = SDCARD_PIN_NUM_MISO, .sclk_io_num = SDCARD_PIN_NUM_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 4000, }; ret = spi_bus_initialize(host.slot, &bus_cfg, SDCARD_DMA_CHANNEL); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize bus."); vTaskDelete(NULL); return; } // 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. sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); slot_config.gpio_cs = SDCARD_PIN_NUM_CS; slot_config.host_id = host.slot; /* * No errors from the sdspi_transaction driver. */ esp_log_level_set("sdspi_transaction", 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_sdspi_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_sdspi_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", 0755); ESP_LOGI(TAG, "mkdir(/sdcard/w)=%d", ret); ret = mkdir("/sdcard/w/log", 0755); ESP_LOGI(TAG, "mkdir(/sdcard/w/log)=%d", ret); } else { closedir(dir); } esp_log_level_set("sdmmc_sd", ESP_LOG_INFO); } } 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_sdspi_unmount(); sdcard_state->card_present = false; esp_log_level_set("sdmmc_sd", ESP_LOG_NONE); } 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)) { log_msg(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, "],\"annotations\":[\n"); // End of brewdata // 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 and brew fclose(f); } sprintf(destname, "/sdcard/w/log/%s.json", sdcard_state->logfile); if (FileCopy(filename, destname) == 0) { log_msg(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); } } }