main/task_sdcard.c

changeset 0
b74b0e4902c3
child 1
ad2c8b13eb88
equal deleted inserted replaced
-1:000000000000 0:b74b0e4902c3
1 /**
2 * @file task_sdcard.c
3 * @brief SD/MMC driver. This driver is for a slot for the user on
4 * the front panel. It will detect an inserted card and then
5 * mount it. But, it cannot detect the removal of a card!!
6 * Also, brew logging is handled here and finished brewlogs
7 * are copied to the SD card if it is mounted.
8 * Recipes to import must go into the /sdcard/recipe folder
9 * and have extension .xml and the contents be a beerxml file.
10 */
11
12
13 #include "vfs_fat_internal.h"
14 #include "driver/sdmmc_host.h"
15 #include "driver/sdspi_host.h"
16 #include "sdmmc_cmd.h"
17 #include "diskio.h"
18
19 #include "config.h"
20
21
22
23 SDCARD_State * sdcard_state;
24 JSON_log * json_log;
25 EventGroupHandle_t xEventGroupSDcard; ///< SD card events.
26
27 static const char *TAG = "task_sdcard";
28 static sdmmc_card_t* s_card = NULL;
29 static uint8_t s_pdrv = 0;
30 static char * s_base_path = NULL;
31
32 BYTE pdrv = 0xFF;
33
34
35 #define SDCARD_HOST_SLOT VSPI_HOST ///< HSPI_HOST is used by the TFT
36 #define SDCARD_PIN_NUM_MISO 2
37 #define SDCARD_PIN_NUM_MOSI 15
38 #define SDCARD_PIN_NUM_CLK 14
39 #define SDCARD_PIN_NUM_CS 13
40 #define SDCARD_DMA_CHANNEL 2 ///< Channel 1 is used by the TFT
41
42
43 const int TASK_SDCARD_LOG_CLEAN = BIT0; ///< Clean spiffs logfile directory
44 const int TASK_SDCARD_LOG_CLOSE = BIT1; ///< Close the logfile.
45
46 extern time_t now;
47 extern struct tm timeinfo;
48
49
50
51 void log_begin(time_t t)
52 {
53 struct tm timeinfo;
54
55 /*
56 * If there is an open logfile from an crashed brew, open that one.
57 */
58 if (! strlen(sdcard_state->logfile) && strlen(runtime.Logfile)) {
59 sprintf(sdcard_state->logfile, "%s", runtime.Logfile);
60 ESP_LOGI(TAG, "Resumed %s", sdcard_state->logfile);
61 } else {
62 localtime_r(&t, &timeinfo);
63 if (timeinfo.tm_year > (2016 - 1900)) {
64 // Valid time, construct the filename.
65 strftime(sdcard_state->logfile, sizeof(sdcard_state->logfile), "br%y%m%d%H%M", &timeinfo);
66 } else {
67 sprintf(sdcard_state->logfile, "brewlog");
68 }
69 ESP_LOGI(TAG, "Create %s", sdcard_state->logfile);
70 sprintf(runtime.Logfile, "%s", sdcard_state->logfile);
71 }
72 }
73
74
75
76 void log_close(void)
77 {
78 xEventGroupSetBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLOSE);
79 }
80
81
82
83 void log_clean(void)
84 {
85 xEventGroupSetBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLEAN);
86 }
87
88
89
90 void log_json(void)
91 {
92 char filename[32], strftime_buf[64];
93 FILE *f;
94 bool addcomma = true;
95
96 if (strlen(sdcard_state->logfile)) {
97 sprintf(filename, "/spiffs/log/%s.json", sdcard_state->logfile);
98 f = fopen(filename, "r");
99 if (f == NULL) {
100 // Create the file and add the header
101 f = fopen(filename, "a");
102 if (f) {
103 strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
104 fprintf(f, "{\n");
105 fprintf(f, " \"brew\": [{\n");
106 fprintf(f, " \"Recipe\":\"%s\",\n", recipe.Name);
107 fprintf(f, " \"Date\":\"%s\",\n", strftime_buf);
108 fprintf(f, " \"brewdata\":[\n");
109 addcomma = false;
110 fclose(f);
111 } else {
112 ESP_LOGE(TAG, "Failed to create %s error %d", filename, errno);
113 return;
114 }
115 } else {
116 fclose(f); // Was open for reading.
117 }
118
119 f = fopen(filename, "a");
120 if (f) {
121 if (addcomma) {
122 fprintf(f, ",\n");
123 }
124 addcomma = true;
125 fprintf(f, " {\"MLT_sp\":\"%.3f\",\"MLT_pv\":\"%.3f\",\"MLT_pwm\":\"%d\",\"MLT_tr\":\"%d\",\"Pump\":\"%d\",",
126 json_log->mlt_sp, json_log->mlt_pv, json_log->mlt_power, json_log->mlt_tempreached, json_log->pump_run);
127 if (json_log->hlt_sp > 0.0) {
128 fprintf(f, "\"HLT_sp\":\"%.3f\",\"HLT_pv\":\"%.3f\",\"HLT_pwm\":\"%d\",", json_log->hlt_sp, json_log->hlt_pv, json_log->hlt_power);
129 }
130 fprintf(f, "\"Label\":\"%s\"}", json_log->time);
131 printf("{\"MLT_sp\":\"%.3f\",\"MLT_pv\":\"%.3f\",\"MLT_pwm\":\"%d\",\"MLT_tr\":\"%d\",\"Pump\":\"%d\",",
132 json_log->mlt_sp, json_log->mlt_pv, json_log->mlt_power, json_log->mlt_tempreached, json_log->pump_run);
133 if (json_log->hlt_sp > 0.0) {
134 printf("\"HLT_sp\":\"%.3f\",\"HLT_pv\":\"%.3f\",\"HLT_pwm\":\"%d\",", json_log->hlt_sp, json_log->hlt_pv, json_log->hlt_power);
135 }
136 printf("\"Label\":\"%s\"}\n", json_log->time);
137 fclose(f);
138 }
139 }
140 }
141
142
143
144 void log_annotation(int annotation_type, char *label)
145 {
146 char filename[32];
147 char bordercolor[9], color[9], pos[8];
148 FILE *f;
149 bool addcomma = true;
150
151 if (strlen(sdcard_state->logfile)) {
152 sprintf(filename, "/spiffs/log/%s.anno", sdcard_state->logfile);
153
154 switch (annotation_type) {
155 case ANNOTATION_STAGE: snprintf(bordercolor, 8, "#8942f4");
156 snprintf(color, 8, "#00215b");
157 snprintf(pos, 7, "bottom");
158 break;
159
160 case ANNOTATION_EVENT: snprintf(bordercolor, 8, "#42f445");
161 snprintf(color, 8, "#00215b");
162 snprintf(pos, 7, "top");
163 break;
164
165 case ANNOTATION_SYSTEM: snprintf(bordercolor, 8, "black");
166 snprintf(color, 8, "red");
167 snprintf(pos, 7, "center");
168 break;
169 }
170
171 // Check if the file exists to see if we need to insert a comma.
172 f = fopen(filename, "r");
173 if (f == NULL) {
174 addcomma = false;
175 } else {
176 fclose(f);
177 }
178
179 f = fopen(filename, "a");
180 if (f) {
181 if (addcomma) {
182 fprintf(f, ",\n");
183 }
184 addcomma = true;
185 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);
186 fclose(f);
187 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);
188 }
189 }
190 }
191
192
193
194 /*
195 * This is a local modified version of the esp_vfs_fat_sdmmc_mount() function in
196 * the FreeRTOS components library. It is here so we can better handle errors
197 * for our application.
198 */
199 esp_err_t my_vfs_fat_sdmmc_init(const char* base_path, const sdmmc_host_t* host_config, const void* slot_config)
200 {
201 if (s_card != NULL) {
202 return ESP_ERR_INVALID_STATE;
203 }
204
205 // connect SDMMC driver to FATFS
206 pdrv = 0xFF;
207 if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == 0xFF) {
208 ESP_LOGI(TAG, "the maximum count of volumes is already mounted");
209 return ESP_ERR_NO_MEM;
210 }
211
212 s_base_path = strdup(base_path);
213 if (!s_base_path) {
214 ESP_LOGI(TAG, "could not copy base_path");
215 return ESP_ERR_NO_MEM;
216 }
217 esp_err_t err = ESP_OK;
218 s_card = malloc(sizeof(sdmmc_card_t));
219 if (s_card == NULL) {
220 err = ESP_ERR_NO_MEM;
221 goto fail;
222 }
223
224 err = (*host_config->init)();
225 if (err != ESP_OK) {
226 ESP_LOGI(TAG, "host init returned rc=0x%x", err);
227 goto fail;
228 }
229
230 // configure SD slot
231 if (host_config->flags == SDMMC_HOST_FLAG_SPI) {
232 err = sdspi_host_init_slot(host_config->slot, (const sdspi_slot_config_t*) slot_config);
233 } else {
234 err = sdmmc_host_init_slot(host_config->slot, (const sdmmc_slot_config_t*) slot_config);
235 }
236 if (err != ESP_OK) {
237 ESP_LOGI(TAG, "slot_config returned rc=0x%x", err);
238 goto fail;
239 }
240 return ESP_OK;
241
242 fail:
243 host_config->deinit();
244 free(s_card);
245 s_card = NULL;
246 return err;
247 }
248
249
250
251 esp_err_t my_esp_vfs_fat_sdmmc_mount(const char* base_path,
252 const sdmmc_host_t* host_config,
253 const void* slot_config,
254 const esp_vfs_fat_mount_config_t* mount_config,
255 sdmmc_card_t** out_card)
256 {
257 FATFS* fs = NULL;
258 esp_err_t err = ESP_OK;
259
260 if (s_card == NULL) {
261 return ESP_ERR_INVALID_STATE;
262 }
263
264 // probe and initialize card
265 err = sdmmc_card_init(host_config, s_card);
266 if (err != ESP_OK) {
267 if (err != ESP_ERR_INVALID_RESPONSE) { // No card present, do not log
268 ESP_LOGI(TAG, "sdmmc_card_init failed 0x(%x)", err);
269 }
270 goto fail;
271 }
272 if (out_card != NULL) {
273 *out_card = s_card;
274 }
275
276 ff_diskio_register_sdmmc(pdrv, s_card);
277 s_pdrv = pdrv;
278 ESP_LOGD(TAG, "using pdrv=%i", pdrv);
279 char drv[3] = {(char)('0' + pdrv), ':', 0};
280
281 // connect FATFS to VFS
282 err = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
283 if (err == ESP_ERR_INVALID_STATE) {
284 // it's okay, already registered with VFS
285 } else if (err != ESP_OK) {
286 ESP_LOGI(TAG, "esp_vfs_fat_register failed 0x(%x)", err);
287 goto fail;
288 }
289
290 // Try to mount partition
291 FRESULT res = f_mount(fs, drv, 1);
292 if (res != FR_OK) {
293 err = ESP_FAIL;
294 ESP_LOGD(TAG, "f_mount failed (%d)", res);
295 goto fail;
296 }
297 return ESP_OK;
298
299 fail:
300 if (fs) {
301 f_mount(NULL, drv, 0);
302 }
303 esp_vfs_fat_unregister_path(base_path);
304 ff_diskio_unregister(pdrv);
305 return err;
306 }
307
308
309
310 esp_err_t my_esp_vfs_fat_sdmmc_unmount()
311 {
312 if (s_card == NULL) {
313 return ESP_ERR_INVALID_STATE;
314 }
315 // unmount
316 char drv[3] = {(char)('0' + s_pdrv), ':', 0};
317 f_mount(0, drv, 0);
318 return ESP_OK;
319 }
320
321
322
323 int FileCopy(char *ff, char *tf)
324 {
325 FILE *f, *t;
326 uint8_t buf[512];
327 size_t bytes;
328
329 f = fopen(ff, "r");
330 if (f == NULL) {
331 ESP_LOGE(TAG, "FileCopy cannot open %s for read, error %d", ff, errno);
332 return 1;
333 }
334
335 t = fopen(tf, "w+");
336 if (t == NULL) {
337 ESP_LOGE(TAG, "FileCopy cannot open %s for create/write, error %d", tf, errno);
338 fclose(f);
339 return 1;
340 }
341
342 while ((bytes = fread(&buf, 1, 512, f))) {
343 fwrite(&buf, 1, bytes, t);
344 vTaskDelay(10 / portTICK_PERIOD_MS);
345 }
346
347 fclose(f);
348 fclose(t);
349 return 0;
350 }
351
352
353
354 void SyncDirs(char *fromdir, char *todir)
355 {
356 char ff[64], tf[64];
357 struct stat fs, ts;
358 int rc;
359
360 ESP_LOGI(TAG, "SyncDirs(%s, %s)", fromdir, todir);
361
362 DIR *dir = opendir(fromdir);
363 struct dirent* de = readdir(dir);
364 while (de) {
365 if (de->d_type == DT_REG) {
366 sprintf(ff, "%s/%s", fromdir, de->d_name);
367 if (stat(ff, &fs) == ESP_OK) {
368
369 sprintf(tf, "%s/%s", todir, de->d_name);
370 if (stat(tf, &ts) != ESP_OK) {
371 ts.st_size = 0;
372 }
373
374 if (fs.st_size && (fs.st_size != ts.st_size)) {
375 rc = FileCopy(ff, tf);
376 ESP_LOGI(TAG, "Copy %s to %s, %ld bytes, rc=%d", ff, todir, fs.st_size, rc);
377 }
378 }
379 }
380 de = readdir(dir);
381 vTaskDelay(50 / portTICK_PERIOD_MS);
382 }
383 closedir(dir);
384
385 /*
386 * Now see if we need to remove files.
387 */
388 dir = opendir(todir);
389 de = readdir(dir);
390 while (de) {
391 sprintf(tf, "%s/%s", todir, de->d_name);
392 sprintf(ff, "%s/%s", fromdir, de->d_name);
393
394 if (stat(ff, &fs) != ESP_OK) {
395 if (unlink(tf) == ESP_OK) {
396 ESP_LOGI(TAG, "Removed %s", tf);
397 }
398 }
399
400 de = readdir(dir);
401 vTaskDelay(50 / portTICK_PERIOD_MS);
402 }
403 closedir(dir);
404 }
405
406
407
408 void task_sdcard(void *pvParameter)
409 {
410 sdmmc_card_t* card;
411 esp_err_t ret;
412 EventBits_t uxBits;
413 char filename[64];
414
415 sdcard_state = malloc(sizeof(SDCARD_State));
416 sdcard_state->host_ok = false;
417 sdcard_state->card_present = false;
418 sdcard_state->logfile[0] = '\0';
419
420 json_log = malloc(sizeof(JSON_log));
421
422 ESP_LOGI(TAG, "Starting SD card");
423 sdmmc_host_t host = SDSPI_HOST_DEFAULT();
424 host.slot = SDCARD_HOST_SLOT; // HSPI_HOST is in use by the TFT.
425 sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
426 slot_config.gpio_miso = SDCARD_PIN_NUM_MISO;
427 slot_config.gpio_mosi = SDCARD_PIN_NUM_MOSI;
428 slot_config.gpio_sck = SDCARD_PIN_NUM_CLK;
429 slot_config.gpio_cs = SDCARD_PIN_NUM_CS;
430 slot_config.dma_channel = SDCARD_DMA_CHANNEL;
431 // This initializes the slot without card detect (CD) and write protect (WP) signals.
432 // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
433
434 /*
435 * No errors from the sdmmc_cmd driver.
436 */
437 esp_log_level_set("sdmmc_cmd", ESP_LOG_NONE);
438
439 /*
440 * Options for mounting the filesystem.
441 * If format_if_mount_failed is set to true, SD card will be partitioned and
442 * formatted in case when mounting fails.
443 */
444 esp_vfs_fat_sdmmc_mount_config_t mount_config = {
445 .format_if_mount_failed = false,
446 .max_files = 5,
447 .allocation_unit_size = 16 * 1024
448 };
449
450 ret = my_vfs_fat_sdmmc_init("/sdcard", &host, &slot_config);
451 if (ret == ESP_OK) {
452 ESP_LOGI(TAG, "SPI card interface ready");
453 sdcard_state->host_ok = true;
454 } else {
455 ESP_LOGE(TAG, "SPI card interface failed, abort task");
456 vTaskDelete(NULL);
457 return;
458 }
459
460 xEventGroupSDcard = xEventGroupCreate();
461
462 /*
463 * Task loop, continues check of the inserted cards.
464 */
465 while (1) {
466
467 if (sdcard_state->card_present == false) {
468 /*
469 * If the card is not mounted, try it.
470 */
471 ret = my_esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
472 if (ret == ESP_OK) {
473 ESP_LOGI(TAG, "SD card mounted on /sdcard");
474 sdcard_state->card_present = true;
475
476 DIR* dir = opendir("/sdcard/w/log");
477 if (dir == NULL) {
478 ret = mkdir("/sdcard/w/log", 0755);
479 printf("Dir created ret=%d\n", ret);
480 } else {
481 closedir(dir);
482 }
483
484
485 SyncDirs("/sdcard/w", "/spiffs/w");
486 SyncDirs("/sdcard/w/js", "/spiffs/w/js");
487 SyncDirs("/sdcard/w/css", "/spiffs/w/css");
488 // SyncDirs("/sdcard/w/js/modl" , "/spiffs/w/js/modl"); //
489 // SyncDirs("/sdcard/w/js/utils", "/spiffs/w/js/utils");
490 // SyncDirs("/sdcard/w/js/zlib", "/spiffs/w/js/zlib");
491 // SyncDirs("/sdcard/w/core", "/spiffs/w/core");
492 // SyncDirs("/sdcard/w/core/input", "/spiffs/w/core/input");
493 // SyncDirs("/sdcard/w/core/util", "/spiffs/w/core/util");
494 // SyncDirs("/sdcard/w/app", "/spiffs/w/app");
495 // SyncDirs("/sdcard/w/app/images", "/spiffs/w/app/images");
496 // SyncDirs("/sdcard/w/app/locale", "/spiffs/w/app/locale");
497 // SyncDirs("/sdcard/w/app/sounds", "/spiffs/w/app/sounds"); //
498 SyncDirs("/sdcard/w/app/styles", "/spiffs/w/app/styles");
499 // SyncDirs("/sdcard/fonts", "/spiffs/fonts"); //
500
501 }
502 } else {
503 /*
504 * Check if the mounted card is still in the slot.
505 */
506 DIR* dir = opendir("/sdcard/w/log");
507 if (dir == NULL) {
508 ESP_LOGI(TAG, "SD card missing, unmount");
509 my_esp_vfs_fat_sdmmc_unmount();
510 sdcard_state->card_present = false;
511 } else {
512 closedir(dir);
513 }
514 }
515
516 uxBits = xEventGroupWaitBits(xEventGroupSDcard,
517 TASK_SDCARD_LOG_CLEAN | TASK_SDCARD_LOG_CLOSE, pdFALSE, pdFALSE, 1000 / portTICK_PERIOD_MS);
518
519 if (uxBits & TASK_SDCARD_LOG_CLEAN) {
520 DIR *dir = opendir("/spiffs/log");
521 char lf[32];
522
523 if (dir != NULL) {
524 struct dirent *de = readdir(dir);
525 while (de) {
526 sprintf(lf, "/spiffs/log/%s", de->d_name);
527 if (unlink(lf) == ESP_OK) {
528 ESP_LOGI(TAG, "Removed old %s", lf);
529 }
530 de = readdir(dir);
531 vTaskDelay(2 / portTICK_PERIOD_MS);
532 }
533 closedir(dir);
534 }
535 xEventGroupClearBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLEAN);
536 }
537
538 if (uxBits & TASK_SDCARD_LOG_CLOSE) {
539 // Close the logfile.
540 if (strlen(sdcard_state->logfile) && (sdcard_state->card_present == true)) {
541 ESP_LOGI(TAG, "Closing logfile");
542 char destname[64];
543 sprintf(destname, "/sdcard/w/log");
544 int rc = mkdir(destname, 0755);
545 if (rc && (errno != EEXIST)) {
546 ESP_LOGE(TAG, "Cannot create %s error %d", destname, errno);
547 } else {
548 sprintf(filename, "/spiffs/log/%s.json", sdcard_state->logfile);
549 // First close the JSON data records
550 FILE *f = fopen(filename, "a+");
551 if (f) {
552 fprintf(f, " ],\n"); // End of brewdata
553 fprintf(f, " \"annotations\":[\n");
554 // Insert annotation records
555 sprintf(destname, "/spiffs/log/%s.anno", sdcard_state->logfile);
556 FILE *a = fopen(destname, "r");
557 char buf[256];
558 if (a) {
559 while(true) {
560 if (fgets(buf, sizeof(buf), a)) {
561 fprintf(f, "%s", buf);
562 } else {
563 break;
564 }
565 }
566 fclose(a);
567 unlink(destname);
568 }
569 fprintf(f, " ]\n"); // End of annotations
570 fprintf(f, " }]\n"); // End of brew
571 fprintf(f, "}\n");
572 fclose(f);
573 }
574 sprintf(destname, "/sdcard/w/log/%s.json", sdcard_state->logfile);
575 if (FileCopy(filename, destname) == 0) {
576 ESP_LOGI(TAG, "JSON file copied to %s", destname);
577 unlink(filename);
578 }
579 }
580 }
581 sdcard_state->logfile[0] = '\0'; // Clear logfile name
582 runtime.Logfile[0] = '\0';
583 xEventGroupClearBits(xEventGroupSDcard, TASK_SDCARD_LOG_CLOSE);
584 }
585 }
586 }
587
588

mercurial