// sd_logger.cpp - SD Card Logger Implementation with Batch Writing #include #include "sd_logger.h" #include "task_config.h" #include "can_handler.h" #include "psram_buffer.h" extern volatile uint32_t canErrorCount; bool sdInitialized = false; uint64_t sdCardSize = 0; uint64_t sdFreeSpace = 0; File currentLogFile; char currentLogFileName[64] = {0}; uint32_t currentLogFileSize = 0; static uint32_t fileSequenceNumber = 0; static uint8_t* writeBuffer = nullptr; static size_t bufferOffset = 0; static uint32_t lastFlushTime = 0; static uint32_t framesInBuffer = 0; bool initSDCard() { Serial.println("Initializing SD Card (SDIO 4-bit mode)..."); writeBuffer = getSDWriteBuffer(); if (writeBuffer == nullptr) { if (psramFound()) { writeBuffer = (uint8_t*)ps_malloc(PSRAM_SD_BUFFER_SIZE); } else { writeBuffer = (uint8_t*)malloc(PSRAM_SD_BUFFER_SIZE); } if (writeBuffer == nullptr) { Serial.println("Failed to allocate SD write buffer!"); return false; } } SD_MMC.setPins(SDIO_CLK_PIN, SDIO_CMD_PIN, SDIO_D0_PIN, SDIO_D1_PIN, SDIO_D2_PIN, SDIO_D3_PIN); if (!SD_MMC.begin("/sdcard", false)) { Serial.println("SD Card mount failed!"); sdInitialized = false; return false; } sdCardSize = SD_MMC.cardSize(); sdFreeSpace = SD_MMC.totalBytes() - SD_MMC.usedBytes(); Serial.println("SD Card mounted successfully!"); Serial.printf(" Card Size: %llu MB\n", sdCardSize / (1024 * 1024)); Serial.printf(" Free Space: %llu MB\n", sdFreeSpace / (1024 * 1024)); sdInitialized = true; createLogDirectories(); return true; } bool createLogDirectories() { if (!sdInitialized) return false; // Create logs directory if (!SD_MMC.exists(LOGS_DIR)) { if (SD_MMC.mkdir(LOGS_DIR)) { Serial.println("Created /logs directory"); } else { Serial.println("Failed to create /logs directory"); return false; } } // Create config directory if (!SD_MMC.exists(CONFIG_DIR)) { if (SD_MMC.mkdir(CONFIG_DIR)) { Serial.println("Created /config directory"); } else { Serial.println("Failed to create /config directory"); return false; } } return true; } bool startLogFile() { if (!sdInitialized) return false; // Close current file if open closeLogFile(); // Generate filename with timestamp time_t now = time(NULL); struct tm* timeinfo = localtime(&now); snprintf(currentLogFileName, sizeof(currentLogFileName), "%s/CAN_%04d%02d%02d_%02d%02d%02d_%03d.pcap", LOGS_DIR, timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, fileSequenceNumber++); // Open file for writing currentLogFile = SD_MMC.open(currentLogFileName, FILE_WRITE); if (!currentLogFile) { Serial.printf("Failed to open log file: %s\n", currentLogFileName); return false; } // Write PCAP global header PCAPGlobalHeader header; header.magicNumber = PCAP_MAGIC_NUMBER; header.versionMajor = 2; header.versionMinor = 4; header.thiszone = 0; header.sigfigs = 0; header.snaplen = 65535; header.network = PCAP_LINK_TYPE; currentLogFile.write((uint8_t*)&header, sizeof(header)); currentLogFileSize = sizeof(header); Serial.printf("Started new log file: %s\n", currentLogFileName); return true; } void closeLogFile() { flushWriteBuffer(); if (currentLogFile) { currentLogFile.close(); Serial.printf("Closed log file: %s\n", currentLogFileName); currentLogFileName[0] = '\0'; currentLogFileSize = 0; } } bool writeCANFrameToSD(const CanFrame& frame) { if (!sdInitialized || !currentLogFile) { return false; } if (currentLogFileSize >= FILE_SPLIT_SIZE) { flushWriteBuffer(); closeLogFile(); if (!startLogFile()) { return false; } } PCAPPacketHeader pktHeader; pktHeader.tsSec = (uint32_t)(frame.timestamp / 1000000ULL); pktHeader.tsUsec = (uint32_t)(frame.timestamp % 1000000ULL); CANFDFrame canFrame; canFrame.canId = frame.id; canFrame.len = frame.len; canFrame.flags = frame.flags; canFrame.reserved0 = 0; canFrame.reserved1 = 0; memcpy(canFrame.data, frame.data, frame.len); uint32_t frameSize = sizeof(CANFDFrame) - 64 + frame.len; pktHeader.inclLen = frameSize; pktHeader.origLen = frameSize; size_t totalSize = sizeof(pktHeader) + frameSize; if (bufferOffset + totalSize > PSRAM_SD_BUFFER_SIZE) { if (!flushWriteBuffer()) { return false; } } if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(100)) == pdTRUE) { memcpy(writeBuffer + bufferOffset, &pktHeader, sizeof(pktHeader)); bufferOffset += sizeof(pktHeader); memcpy(writeBuffer + bufferOffset, &canFrame, frameSize); bufferOffset += frameSize; framesInBuffer++; currentLogFileSize += totalSize; xSemaphoreGive(sdMutex); uint32_t now = millis(); if (now - lastFlushTime >= SD_FLUSH_INTERVAL_MS) { flushWriteBuffer(); lastFlushTime = now; } return true; } return false; } bool flushWriteBuffer() { if (bufferOffset == 0 || !currentLogFile) { return true; } if (xSemaphoreTake(sdMutex, pdMS_TO_TICKS(500)) == pdTRUE) { size_t written = currentLogFile.write(writeBuffer, bufferOffset); currentLogFile.flush(); xSemaphoreGive(sdMutex); if (written != bufferOffset) { Serial.printf("SD write incomplete: %d of %d bytes\n", written, bufferOffset); bufferOffset = 0; framesInBuffer = 0; return false; } bufferOffset = 0; framesInBuffer = 0; return true; } return false; } void sdWriteTask(void *pvParameters) { Serial.println("SD Write Task started on Core 1"); CanFrame frame; uint32_t lastStatusTime = 0; uint32_t framesWritten = 0; uint32_t framesFromPSRAM = 0; while (1) { bool gotFrame = false; if (xQueueReceive(canRxQueue, &frame, pdMS_TO_TICKS(10)) == pdTRUE) { gotFrame = true; } else if (canFrameBuffer.available() > 0) { if (canFrameBuffer.pop(frame)) { gotFrame = true; framesFromPSRAM++; } } if (gotFrame) { if (!writeCANFrameToSD(frame)) { canErrorCount++; } else { framesWritten++; } } uint32_t now = millis(); if (now - lastStatusTime > 10000) { Serial.printf("[SD] Written: %d, From PSRAM: %d, Buffer: %d, Q: %d, PSRAM: %d\n", framesWritten, framesFromPSRAM, bufferOffset, uxQueueMessagesWaiting(canRxQueue), canFrameBuffer.available()); lastStatusTime = now; } } } uint64_t getSDCardSize() { return sdInitialized ? sdCardSize : 0; } uint64_t getFreeSpace() { if (!sdInitialized) return 0; return SD_MMC.totalBytes() - SD_MMC.usedBytes(); } uint64_t getUsedSpace() { if (!sdInitialized) return 0; return SD_MMC.usedBytes(); } int listLogFiles(char* buffer, size_t bufferSize) { if (!sdInitialized) return 0; File root = SD_MMC.open(LOGS_DIR); if (!root || !root.isDirectory()) { return 0; } int count = 0; size_t offset = 0; File file = root.openNextFile(); while (file && offset < bufferSize - 128) { if (!file.isDirectory() && strstr(file.name(), ".pcap")) { int len = snprintf(buffer + offset, bufferSize - offset, "{\"name\":\"%s\",\"size\":%d},", file.name(), file.size()); offset += len; count++; } file = root.openNextFile(); } // Remove trailing comma if (offset > 0 && buffer[offset - 1] == ',') { buffer[offset - 1] = '\0'; } return count; } bool getLogFileInfo(const char* filename, uint32_t& size, uint32_t& timestamp) { if (!sdInitialized) return false; char path[128]; snprintf(path, sizeof(path), "%s/%s", LOGS_DIR, filename); File file = SD_MMC.open(path); if (!file) return false; size = file.size(); timestamp = file.getLastWrite(); file.close(); return true; } bool deleteLogFile(const char* filename) { if (!sdInitialized) return false; char path[128]; snprintf(path, sizeof(path), "%s/%s", LOGS_DIR, filename); return SD_MMC.remove(path); } const char* getCurrentLogFilename() { return currentLogFileName; }