#include "SD_Card.h" // ── Globals ────────────────────────────────────────────────────────────────── Memory g_memories[MAX_MEMORIES]; int g_memoryCount = 0; int g_currentIndex = 0; uint8_t g_volume = 12; uint16_t SDCard_Size = 0; uint16_t Flash_Size = 0; // Tribute Alarm Settings Globals bool g_tributeAlarmEnabled = false; int g_tributeAlarmHour = 19; int g_tributeAlarmMinute = 0; int g_timezoneOffset = 0; bool g_timeSynchronized = false; // ── SD Init ─────────────────────────────────────────────────────────────────── void SD_Init() { if (!SD_MMC.setPins(SD_CLK_PIN, SD_CMD_PIN, SD_D0_PIN)) { printf("SD MMC: Pin change failed!\r\n"); return; } if (!SD_MMC.begin("/sdcard", true, true)) { printf("SD card initialization failed!\r\n"); return; } printf("SD card OK (1-bit mode)\r\n"); uint8_t cardType = SD_MMC.cardType(); if (cardType == CARD_NONE) { printf("No SD card attached\r\n"); return; } uint64_t total = SD_MMC.totalBytes(); uint64_t used = SD_MMC.usedBytes(); SDCard_Size = total / (1024 * 1024); printf("SD: %llu MB total, %llu MB used\r\n", total/(1024*1024), used/(1024*1024)); // Ensure required directories exist if (!SD_MMC.exists("/photos")) SD_MMC.mkdir("/photos"); if (!SD_MMC.exists("/audio")) SD_MMC.mkdir("/audio"); } void Flash_test() { uint32_t flashSize = ESP.getFlashChipSize(); Flash_Size = flashSize / 1024 / 1024; printf("Flash: %d MB\r\n", Flash_Size); } // ── Config helpers ──────────────────────────────────────────────────────────── // We use a dead-simple line-based text format so we don't need ArduinoJson: // ID|name|photo_filename|audio_filename // Volume is stored on its own line: VOLUME|12 // This is robust against partial reads and avoids heap fragmentation. static const char* CONFIG_PATH = "/config.txt"; bool Config_Load() { g_memoryCount = 0; g_currentIndex = 0; g_volume = 12; File f = SD_MMC.open(CONFIG_PATH, "r"); if (!f) { printf("Config: no config file found, starting fresh\r\n"); return true; // not an error — first boot } char line[200]; while (f.available()) { // Read a line int len = 0; while (f.available() && len < (int)sizeof(line) - 1) { char c = f.read(); if (c == '\n') break; line[len++] = c; } line[len] = '\0'; // Trim trailing \r if (len > 0 && line[len-1] == '\r') { line[len-1] = '\0'; len--; } if (len == 0) continue; if (strncmp(line, "VOLUME|", 7) == 0) { g_volume = (uint8_t)atoi(line + 7); if (g_volume > 21) g_volume = 12; continue; } if (strncmp(line, "INDEX|", 6) == 0) { g_currentIndex = atoi(line + 6); continue; } if (strncmp(line, "ALARM|", 6) == 0) { // Parse: ALARM|enabled|hour|minute|timezone_offset char* tok = strtok(line + 6, "|"); if (tok) g_tributeAlarmEnabled = (atoi(tok) != 0); tok = strtok(NULL, "|"); if (tok) g_tributeAlarmHour = atoi(tok); tok = strtok(NULL, "|"); if (tok) g_tributeAlarmMinute = atoi(tok); tok = strtok(NULL, "|"); if (tok) g_timezoneOffset = atoi(tok); continue; } // Parse memory line: id|name|photo|audio if (g_memoryCount >= MAX_MEMORIES) continue; char* tok = strtok(line, "|"); if (!tok) continue; int id = atoi(tok); tok = strtok(NULL, "|"); if (!tok) continue; char name[MAX_NAME_LEN]; strncpy(name, tok, MAX_NAME_LEN-1); name[MAX_NAME_LEN-1]='\0'; tok = strtok(NULL, "|"); if (!tok) continue; char photo[MAX_FILE_LEN]; strncpy(photo, tok, MAX_FILE_LEN-1); photo[MAX_FILE_LEN-1]='\0'; tok = strtok(NULL, "|"); if (!tok) continue; char audio[MAX_FILE_LEN]; strncpy(audio, tok, MAX_FILE_LEN-1); audio[MAX_FILE_LEN-1]='\0'; Memory& m = g_memories[g_memoryCount++]; m.id = id; strncpy(m.name, name, MAX_NAME_LEN-1); m.name[MAX_NAME_LEN-1] = '\0'; strncpy(m.photo, photo, MAX_FILE_LEN-1); m.photo[MAX_FILE_LEN-1] = '\0'; strncpy(m.audio, audio, MAX_FILE_LEN-1); m.audio[MAX_FILE_LEN-1] = '\0'; } f.close(); // Clamp currentIndex if (g_memoryCount > 0 && g_currentIndex >= g_memoryCount) g_currentIndex = 0; printf("Config: loaded %d memories, volume=%d\r\n", g_memoryCount, g_volume); return true; } bool Config_Save() { File f = SD_MMC.open(CONFIG_PATH, "w"); if (!f) { printf("Config: failed to open for writing\r\n"); return false; } f.printf("VOLUME|%d\n", g_volume); f.printf("INDEX|%d\n", g_currentIndex); f.printf("ALARM|%d|%d|%d|%d\n", g_tributeAlarmEnabled ? 1 : 0, g_tributeAlarmHour, g_tributeAlarmMinute, g_timezoneOffset); for (int i = 0; i < g_memoryCount; i++) { Memory& m = g_memories[i]; f.printf("%d|%s|%s|%s\n", m.id, m.name, m.photo, m.audio); } f.close(); printf("Config: saved %d memories\r\n", g_memoryCount); return true; } int Config_Add_Memory(const char* name, const char* photo, const char* audio) { if (g_memoryCount >= MAX_MEMORIES) return -1; // Generate a new unique id (max existing + 1) int newId = 1; for (int i = 0; i < g_memoryCount; i++) if (g_memories[i].id >= newId) newId = g_memories[i].id + 1; Memory& m = g_memories[g_memoryCount]; m.id = newId; strncpy(m.name, name, MAX_NAME_LEN-1); m.name[MAX_NAME_LEN-1] = '\0'; strncpy(m.photo, photo, MAX_FILE_LEN-1); m.photo[MAX_FILE_LEN-1] = '\0'; strncpy(m.audio, audio, MAX_FILE_LEN-1); m.audio[MAX_FILE_LEN-1] = '\0'; g_memoryCount++; Config_Save(); printf("Config: added memory id=%d name='%s'\r\n", newId, name); return newId; } bool Config_Delete_Memory(int id) { for (int i = 0; i < g_memoryCount; i++) { if (g_memories[i].id == id) { // Optionally delete the files from SD char path[80]; snprintf(path, sizeof(path), "/photos/%s", g_memories[i].photo); SD_MMC.remove(path); snprintf(path, sizeof(path), "/audio/%s", g_memories[i].audio); SD_MMC.remove(path); // Shift array down for (int j = i; j < g_memoryCount - 1; j++) g_memories[j] = g_memories[j+1]; g_memoryCount--; if (g_currentIndex >= g_memoryCount && g_memoryCount > 0) g_currentIndex = g_memoryCount - 1; Config_Save(); printf("Config: deleted memory id=%d\r\n", id); return true; } } return false; } // ── Legacy helpers ───────────────────────────────────────────────────────────── bool File_Search(const char* directory, const char* fileName) { File dir = SD_MMC.open(directory); if (!dir) return false; File file = dir.openNextFile(); while (file) { if (strcmp(file.name(), fileName) == 0) { dir.close(); return true; } file = dir.openNextFile(); } dir.close(); return false; } uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][100], uint16_t maxFiles) { File dir = SD_MMC.open(directory); if (!dir) { printf("Folder not found: %s\r\n", directory); return 0; } uint16_t count = 0; File file = dir.openNextFile(); while (file && count < maxFiles) { if (!file.isDirectory() && strstr(file.name(), fileExtension)) { strncpy(File_Name[count], file.name(), 99); File_Name[count][99] = '\0'; count++; } file = dir.openNextFile(); } dir.close(); return count; } void remove_file_extension(char* file_name) { char* last_dot = strrchr(file_name, '.'); if (last_dot) *last_dot = '\0'; }