diff --git a/sdcard_task.cpp b/sdcard_task.cpp new file mode 100644 index 0000000..d0a07e6 --- /dev/null +++ b/sdcard_task.cpp @@ -0,0 +1,198 @@ +#include "sdcard_task.h" +#include "serial_task.h" +#include + +static SPIClass hspi(HSPI); +volatile bool sdLoggingActive = false; +char currentLogFileName[64] = ""; +static File logFile; +static bool sdReady = false; +static SemaphoreHandle_t sdMutex = NULL; + +void sdTaskInit() { + sdMutex = xSemaphoreCreateMutex(); + + hspi.begin(SD_HSPI_CLK, SD_HSPI_MISO, SD_HSPI_MOSI, SD_HSPI_CS); + + if (!SD.begin(SD_HSPI_CS, hspi, 4000000)) { + Serial.println("[SD] Card mount FAILED!"); + sdReady = false; + } else { + uint8_t cardType = SD.cardType(); + if (cardType == CARD_NONE) { + Serial.println("[SD] No card detected!"); + sdReady = false; + } else { + sdReady = true; + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("[SD] Card mounted. Type: %d, Size: %lluMB\n", cardType, cardSize); + hspi.setFrequency(20000000); + + if (!SD.exists(LOG_DIR)) { + SD.mkdir(LOG_DIR); + Serial.println("[SD] Created /logs directory"); + } + sdCreateNewLogFile(); + sdLoggingActive = true; + } + } + + xTaskCreatePinnedToCore(sdLoggingTask, "SDLog", TASK_STACK_SD_LOG, + NULL, TASK_PRIORITY_SD_LOG, NULL, 0); +} + +bool sdCreateNewLogFile() { + if (!sdReady) return false; + xSemaphoreTake(sdMutex, portMAX_DELAY); + + if (logFile) { logFile.flush(); logFile.close(); } + + struct tm timeinfo; + time_t now; + time(&now); + localtime_r(&now, &timeinfo); + + snprintf(currentLogFileName, sizeof(currentLogFileName), + "%s/LOG_%04d%02d%02d_%02d%02d%02d.csv", + LOG_DIR, + timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, + timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + + logFile = SD.open(currentLogFileName, FILE_WRITE); + if (!logFile) { + Serial.printf("[SD] Failed to create: %s\n", currentLogFileName); + xSemaphoreGive(sdMutex); + return false; + } + logFile.print(CSV_HEADER); + logFile.flush(); + Serial.printf("[SD] New log file: %s\n", currentLogFileName); + xSemaphoreGive(sdMutex); + return true; +} + +void sdLoggingTask(void *param) { + Serial.println("[Task] SDLog started on core " + String(xPortGetCoreID())); + + TickType_t lastFlush = xTaskGetTickCount(); + int pendingWrites = 0; + char csvLine[LOG_LINE_MAX_LEN + 64]; + + while (true) { + LogEntry *entry; + if (xQueueReceive(queueSD, &entry, pdMS_TO_TICKS(100)) == pdTRUE) { + if (sdLoggingActive && sdReady && logFile) { + xSemaphoreTake(sdMutex, portMAX_DELAY); + + int len = snprintf(csvLine, sizeof(csvLine), + "\"%s\",\"%c\",\"", entry->timestamp, + entry->direction == 'T' ? 'T' : 'R'); + + for (int i = 0; i < entry->dataLen && len < (int)sizeof(csvLine) - 4; i++) { + char c = entry->data[i]; + if (c == '"') { csvLine[len++] = '"'; csvLine[len++] = '"'; } + else if (c >= 0x20 && c < 0x7F) { csvLine[len++] = c; } + else { len += snprintf(csvLine + len, sizeof(csvLine) - len, "\\x%02X", (uint8_t)c); } + } + len += snprintf(csvLine + len, sizeof(csvLine) - len, "\"\r\n"); + logFile.write((uint8_t*)csvLine, len); + pendingWrites++; + + xSemaphoreGive(sdMutex); + } + vPortFree(entry); + } + + if (pendingWrites > 0 && + (xTaskGetTickCount() - lastFlush) > pdMS_TO_TICKS(SD_WRITE_INTERVAL)) { + if (logFile) { + xSemaphoreTake(sdMutex, portMAX_DELAY); + logFile.flush(); + xSemaphoreGive(sdMutex); + } + pendingWrites = 0; + lastFlush = xTaskGetTickCount(); + } + + if (pendingWrites >= 50) { + if (logFile) { + xSemaphoreTake(sdMutex, portMAX_DELAY); + logFile.flush(); + xSemaphoreGive(sdMutex); + } + pendingWrites = 0; + lastFlush = xTaskGetTickCount(); + } + } +} + +void sdStopLogging() { + sdLoggingActive = false; + if (logFile) { + xSemaphoreTake(sdMutex, portMAX_DELAY); + logFile.flush(); + logFile.close(); + xSemaphoreGive(sdMutex); + } + Serial.println("[SD] Logging stopped"); +} + +void sdStartLogging() { + if (!sdReady) { Serial.println("[SD] Card not ready!"); return; } + if (!logFile || !sdLoggingActive) sdCreateNewLogFile(); + sdLoggingActive = true; + Serial.println("[SD] Logging started"); +} + +String sdGetFileList() { + if (!sdReady) return "{\"files\":[]}"; + xSemaphoreTake(sdMutex, portMAX_DELAY); + + String json = "{\"files\":["; + File dir = SD.open(LOG_DIR); + bool first = true; + + if (dir && dir.isDirectory()) { + File file = dir.openNextFile(); + while (file) { + if (!file.isDirectory()) { + if (!first) json += ","; + first = false; + json += "{\"name\":\""; + json += file.name(); + json += "\",\"size\":"; + json += String(file.size()); + time_t t = file.getLastWrite(); + struct tm *tm = localtime(&t); + char timeBuf[32]; + strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", tm); + json += ",\"modified\":\""; + json += timeBuf; + json += "\"}"; + } + file = dir.openNextFile(); + } + dir.close(); + } + json += "]}"; + xSemaphoreGive(sdMutex); + return json; +} + +bool sdDeleteFile(const char *filename) { + if (!sdReady) return false; + xSemaphoreTake(sdMutex, portMAX_DELAY); + + char path[128]; + snprintf(path, sizeof(path), "%s/%s", LOG_DIR, filename); + if (sdLoggingActive && strcmp(path, currentLogFileName) == 0) { + xSemaphoreGive(sdMutex); + return false; + } + bool result = SD.remove(path); + Serial.printf("[SD] Delete %s: %s\n", path, result ? "OK" : "FAIL"); + xSemaphoreGive(sdMutex); + return result; +} + +bool sdCardPresent() { return sdReady; } diff --git a/sdcard_task.h b/sdcard_task.h new file mode 100644 index 0000000..81b6189 --- /dev/null +++ b/sdcard_task.h @@ -0,0 +1,23 @@ +#ifndef SDCARD_TASK_H +#define SDCARD_TASK_H + +#include +#include +#include +#include +#include +#include "config.h" + +extern volatile bool sdLoggingActive; +extern char currentLogFileName[64]; + +void sdTaskInit(); +void sdLoggingTask(void *param); +bool sdCreateNewLogFile(); +void sdStopLogging(); +void sdStartLogging(); +String sdGetFileList(); +bool sdDeleteFile(const char *filename); +bool sdCardPresent(); + +#endif