Files
sunflower-picture-app/sound_picture_app/SD_Card.cpp

238 lines
8.1 KiB
C++

#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;
int g_favoriteMemoryId = -1;
// ── SD Init ───────────────────────────────────────────────────────────────────
void SD_Init() {
if (!SD_MMC.setPins(SD_CLK_PIN, SD_CMD_PIN, SD_D0_PIN, SD_D1_PIN, SD_D2_PIN, SD_D3_PIN)) {
printf("SD MMC: Pin change failed!\r\n");
return;
}
if (!SD_MMC.begin("/sdcard", false, true)) {
printf("SD card initialization failed!\r\n");
return;
}
printf("SD card OK (4-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, "FAVORITE|", 9) == 0) {
g_favoriteMemoryId = atoi(line + 9);
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("FAVORITE|%d\n", g_favoriteMemoryId);
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';
}