343 lines
8.3 KiB
C++
343 lines
8.3 KiB
C++
// sd_logger.cpp - SD Card Logger Implementation with Batch Writing
|
|
|
|
#include <Arduino.h>
|
|
#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;
|
|
}
|