main/task_sdcard.c

changeset 0
b74b0e4902c3
child 1
ad2c8b13eb88
--- /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);
+	}
+    }
+}
+
+

mercurial