Files
esp32-Serial-Logger/sdcard_task.cpp
2026-02-18 15:49:30 +00:00

201 lines
5.6 KiB
C++

#include "sdcard_task.h"
#include "serial_task.h"
#include <time.h>
static SPIClass vspi(VSPI);
volatile bool sdLoggingActive = false;
char currentLogFileName[64] = "";
static File logFile;
static bool sdReady = false;
static SemaphoreHandle_t sdMutex = NULL;
void sdTaskInit() {
sdMutex = xSemaphoreCreateMutex();
vspi.begin(SD_VSPI_SCLK, SD_VSPI_MISO, SD_VSPI_MOSI, SD_VSPI_CS);
if (!SD.begin(SD_VSPI_CS, vspi, 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);
vspi.setFrequency(20000000);
if (!SD.exists(LOG_DIR)) {
SD.mkdir(LOG_DIR);
Serial.println("[SD] Created /logs directory");
}
// Don't auto-start logging - user starts manually via web UI
sdLoggingActive = false;
Serial.println("[SD] Ready (logging OFF - start via web UI)");
}
}
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');
// Write data as plain string (no hex encoding)
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) { csvLine[len++] = c; }
// Skip control chars (0x00~0x1F) silently
}
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; }