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:
@@ -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';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user