main/task_sdcard.c

Wed, 24 Oct 2018 23:15:04 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Wed, 24 Oct 2018 23:15:04 +0200
changeset 13
8f01b74bf1dd
parent 6
e84200edc852
child 17
f3451031d6c6
permissions
-rw-r--r--

Update /spiffs via internet. http://update.mbse.eu is now the update server.

/**
 * @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.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;



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);
	}
    }
}



/**
 * @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;
}



/**
 * @brief Sync directories.
 * @param fromdir Source directory
 * @param todir Destination directory
 */
#if 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);
}
#endif



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);
		}


//		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