Redesign to keepsake Memory Frame: config file based named memories with beautiful mobile web dashboard, upload wizard, and delete flow

This commit is contained in:
drjones
2026-07-04 02:18:39 -07:00
parent d23b6c245c
commit 1c5be95d4c
9 changed files with 1019 additions and 675 deletions

View File

@@ -1,120 +1,211 @@
#include "SD_Card.h"
bool SDCard_Flag;
bool SDCard_Finish;
uint16_t SDCard_Size;
uint16_t Flash_Size;
// ── 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;
// ── SD Init ───────────────────────────────────────────────────────────────────
void SD_Init() {
if(!SD_MMC.setPins(SD_CLK_PIN, SD_CMD_PIN, SD_D0_PIN)){
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)) { // true = 1-bit mode (avoids GPIO conflict on D3/CS)
printf("SD card initialization successful (4-bit mode)!\r\n");
} else {
if (!SD_MMC.begin("/sdcard", true, true)) {
printf("SD card initialization failed!\r\n");
}
uint8_t cardType = SD_MMC.cardType();
if(cardType == CARD_NONE){
printf("No SD card attached\r\n");
return;
}
else{
printf("SD Card Type: ");
if(cardType == CARD_MMC){
printf("MMC\r\n");
} else if(cardType == CARD_SD){
printf("SDSC\r\n");
} else if(cardType == CARD_SDHC){
printf("SDHC\r\n");
} else {
printf("UNKNOWN\r\n");
}
uint64_t totalBytes = SD_MMC.totalBytes();
uint64_t usedBytes = SD_MMC.usedBytes();
SDCard_Size = totalBytes/(1024*1024);
printf("Total space: %llu\n", totalBytes);
printf("Used space: %llu\n", usedBytes);
printf("Free space: %llu\n", totalBytes - usedBytes);
}
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");
}
bool File_Search(const char* directory, const char* fileName)
{
File Path = SD_MMC.open(directory);
if (!Path) {
printf("Path: <%s> does not exist\r\n",directory);
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;
}
// 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;
}
File file = Path.openNextFile();
f.printf("VOLUME|%d\n", g_volume);
f.printf("INDEX|%d\n", g_currentIndex);
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) {
if (strcmp(directory, "/") == 0)
printf("File '%s%s' found in root directory.\r\n",directory,fileName);
else
printf("File '%s/%s' found in root directory.\r\n",directory,fileName);
Path.close();
return true;
}
file = Path.openNextFile();
if (strcmp(file.name(), fileName) == 0) { dir.close(); return true; }
file = dir.openNextFile();
}
if (strcmp(directory, "/") == 0)
printf("File '%s%s' not found in root directory.\r\n",directory,fileName);
else
printf("File '%s/%s' not found in root directory.\r\n",directory,fileName);
Path.close();
return false;
dir.close();
return false;
}
uint16_t Folder_retrieval(const char* directory, const char* fileExtension, char File_Name[][100],uint16_t maxFiles)
{
File Path = SD_MMC.open(directory);
if (!Path) {
printf("Path: <%s> does not exist\r\n",directory);
return 0;
}
uint16_t fileCount = 0;
char filePath[100];
File file = Path.openNextFile();
while (file && fileCount < maxFiles) {
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[fileCount], file.name(), sizeof(File_Name[fileCount]) - 1);
File_Name[fileCount][sizeof(File_Name[fileCount]) - 1] = '\0'; // Ensure null-termination
if (strcmp(directory, "/") == 0) {
snprintf(filePath, 100, "%s%s", directory, file.name());
} else {
snprintf(filePath, 100, "%s/%s", directory, file.name());
}
printf("File found: %s\r\n", filePath);
fileCount++;
strncpy(File_Name[count], file.name(), 99);
File_Name[count][99] = '\0';
count++;
}
file = Path.openNextFile();
}
Path.close();
if (fileCount > 0) {
printf("%d <%s> files were retrieved\r\n",fileCount,fileExtension);
return fileCount;
} else {
printf("No files with extension '%s' found in directory: %s\r\n", fileExtension, directory);
return 0;
file = dir.openNextFile();
}
dir.close();
return count;
}
void remove_file_extension(char *file_name) {
char *last_dot = strrchr(file_name, '.');
if (last_dot != NULL) {
*last_dot = '\0';
}
}
void Flash_test()
{
printf("/********** RAM Test**********/\r\n");
uint32_t flashSize = ESP.getFlashChipSize();
Flash_Size = flashSize/1024/1024;
printf("Flash size: %d MB \r\n", flashSize/1024/1024);
printf("/******* RAM Test Over********/\r\n\r\n");
void remove_file_extension(char* file_name) {
char* last_dot = strrchr(file_name, '.');
if (last_dot) *last_dot = '\0';
}