[ai] memory optimizations

This commit is contained in:
Stefan Ostermann 2025-11-02 21:51:35 +01:00
parent fd40b663a0
commit b97eb79b91
1 changed files with 72 additions and 64 deletions

View File

@ -36,7 +36,7 @@ void activateSD()
if (!SD.begin(CS_SDCARD))
{
Serial.println("SD initialization failed!");
Serial.println(F("SD initialization failed!"));
}
SDActive = true;
}
@ -112,15 +112,23 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
activateSD();
// Check if file already exists and create backup name if needed
String filepath = "/" + filename;
String filepath;
filepath.reserve(1 + filename.length());
filepath = "/";
filepath += filename;
if (SD.exists(filepath))
{
String baseName = filename.substring(0, filename.lastIndexOf('.'));
String extension = filename.substring(filename.lastIndexOf('.'));
int counter = 1;
filepath.reserve(1 + baseName.length() + 1 + 10 + extension.length());
do
{
filepath = "/" + baseName + "_" + String(counter) + extension;
filepath = "/";
filepath += baseName;
filepath += "_";
filepath += counter;
filepath += extension;
counter++;
} while (SD.exists(filepath) && counter < 100);
@ -162,13 +170,13 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
// ensure we close while holding the lock to keep SD state consistent
request->_tempFile.close();
sd_lock_release();
request->send(500, txt_plain, "Write error");
request->send_P(500, txt_plain, PSTR("Write error"));
return;
}
// Flush data periodically to ensure it's written
if (index % 2048 == 0)
{ // Flush every 2KB
if (index % buffer_size == 0)
{ // Flush every so often
request->_tempFile.flush();
}
sd_lock_release();
@ -186,7 +194,7 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
Serial.print(F("Upload Complete: "));
Serial.print(filename);
Serial.print(", size: ");
Serial.print(F(", size: "));
Serial.println(humanReadableSize(index + len));
// Rebuild directory tree to include new file (guarded)
@ -194,11 +202,11 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
rootNode.buildDirectoryTree("/");
sd_lock_release();
request->send(200, txt_plain, "Upload successful");
request->send_P(200, txt_plain, PSTR("Upload successful"));
}
else
{
request->send(500, txt_plain, "Upload failed");
request->send_P(500, txt_plain, PSTR("Upload failed"));
}
}
}
@ -215,7 +223,7 @@ void handleMoveFile(AsyncWebServerRequest *request)
sd_lock_acquire();
SD.rename(from, to);
sd_lock_release();
Serial.println("Moved file: " + from + " to " + to);
Serial.print(F("Moved file: ")); Serial.print(from); Serial.print(F(" to ")); Serial.println(to);
// Rebuild directory tree to update file list (guarded)
sd_lock_acquire();
rootNode.buildDirectoryTree("/");
@ -224,8 +232,8 @@ void handleMoveFile(AsyncWebServerRequest *request)
}
else
{
Serial.println("File not found: " + from);
request->send(404, txt_plain, "File not found.");
Serial.print(F("File not found: ")); Serial.println(from);
request->send_P(404, txt_plain, PSTR("File not found."));
}
}
@ -240,17 +248,17 @@ void handleDeleteFile(AsyncWebServerRequest *request)
sd_lock_acquire();
SD.remove(filename.c_str());
sd_lock_release();
Serial.println("Deleted file: " + filename);
Serial.print(F("Deleted file: ")); Serial.println(filename);
// Rebuild directory tree to update file list (guarded)
sd_lock_acquire();
rootNode.buildDirectoryTree("/");
sd_lock_release();
request->send(200, txt_plain, "File deleted.");
request->send(200, txt_plain, F("File deleted."));
}
else
{
Serial.println("File not found: " + filename);
request->send(404, txt_plain, "File not found.");
Serial.print(F("File not found: ")); Serial.println(filename);
request->send_P(404, txt_plain, PSTR("File not found."));
}
}
@ -288,12 +296,12 @@ void playSongById(uint16_t id, uint32_t continueSeconds = 0)
if (mp3File.length() == 0)
{
currentNode = nullptr;
Serial.print("Empty file path for ID: ");
Serial.print(F("Empty file path for ID: "));
Serial.println(id);
return;
}
Serial.print("Playing by ID: ");
Serial.print(F("Playing by ID: "));
Serial.println(id);
Serial.println(mp3File);
@ -314,7 +322,7 @@ void playSongById(uint16_t id, uint32_t continueSeconds = 0)
}
}
void playSongByName(String song)
void playSongByName(const String &song)
{
if (song.length() == 0)
{
@ -348,24 +356,24 @@ void playSongByName(String song)
return;
}
Serial.println("Playing song: " + mp3File);
Serial.print(F("Playing song: ")); Serial.println(mp3File);
deactivateRFID();
activateSD();
if (!playFile(mp3File.c_str()))
{
Serial.println("Failed to play file: " + mp3File);
Serial.print(F("Failed to play file: ")); Serial.println(mp3File);
currentNode = nullptr;
return;
}
}
void playSongByPath(String path)
void playSongByPath(const String &path)
{
playFile(path.c_str());
}
void playSongByRFID(String id)
void playSongByRFID(const String &id)
{
if (id.length() == 0)
{
@ -463,7 +471,7 @@ void playSongByRFID(String id)
folderFlatIndex = 0;
DirectoryNode *startNode = folderFlatList[0].first;
int fileIdx = folderFlatList[0].second;
Serial.print("Shuffle start: ");
Serial.print(F("Shuffle start: "));
Serial.println(startNode->getMP3Files()[fileIdx]);
startNode->setCurrentPlaying(startNode->getMP3Files()[fileIdx]);
currentNode = startNode;
@ -553,7 +561,7 @@ void playNextMp3()
currentNode->setSecondsPlayed(0);
}
Serial.print("Advancing to ");
Serial.print(F("Advancing to "));
String mp3File = currentNode->getCurrentPlaying();
// FIXME crash here if last song.
if (mp3File.isEmpty())
@ -598,12 +606,12 @@ void writeSongProgress(const char *filename, uint16_t id, uint32_t seconds)
file.println(seconds);
file.close();
#ifdef DEBUG
Serial.println("Progress written: ID " + String(id) + ", s " + String(seconds));
Serial.print(F("Progress written: ID ")); Serial.print(id); Serial.print(F(", s ")); Serial.println(seconds);
#endif
}
else
{
Serial.print("Error opening file for writing: ");
Serial.print(F("Error opening file for writing: "));
Serial.println(filename);
}
}
@ -614,7 +622,7 @@ boolean readSongProgress(const char *filename)
if (!file)
{
Serial.print("Error opening file for reading: ");
Serial.print(F("Error opening file for reading: "));
Serial.println(filename);
return false;
}
@ -660,13 +668,13 @@ boolean readSongProgress(const char *filename)
// Validate ranges before assignment
if (tempId < 0 || tempId > 65535)
{
Serial.println("Invalid song in progress: " + String(tempId));
Serial.print(F("Invalid song in progress: ")); Serial.println(tempId);
return false;
}
if (tempSeconds > 4294967295UL)
{
Serial.println("Invalid seconds in progress: " + String(tempSeconds));
Serial.print(F("Invalid seconds in progress: ")); Serial.println(tempSeconds);
return false;
}
@ -674,8 +682,8 @@ boolean readSongProgress(const char *filename)
currentSongSeconds = (uint32_t)tempSeconds;
#ifdef DEBUG
Serial.println("Data read from file: " + data);
Serial.println("Parsed ID: " + String(currentSongId) + ", s: " + String(currentSongSeconds));
Serial.print(F("Data read from file: ")); Serial.println(data);
Serial.print(F("Parsed ID: ")); Serial.print(currentSongId); Serial.print(F(", s: ")); Serial.println(currentSongSeconds);
#endif
return true;
@ -683,7 +691,7 @@ boolean readSongProgress(const char *filename)
// Function to save the rfid_map to the mapping file
void saveMappingToFile(const String filename)
void saveMappingToFile(const String &filename)
{
File file = SD.open(filename, FILE_WRITE);
if (file)
@ -728,15 +736,15 @@ void editMapping(AsyncWebServerRequest *request)
rfid_map[rfid] = MappingEntry(song, mode);
saveMappingToFile(getSysDir(mapping_file));
request->send(200, txt_plain, "Mapping updated");
request->send_P(200, txt_plain, PSTR("Mapping updated"));
}
else
{
request->send(400, txt_plain, "Invalid parameters");
request->send_P(400, txt_plain, PSTR("Invalid parameters"));
}
}
void readDataFromFile(String filename)
void readDataFromFile(const String &filename)
{
File file = SD.open(filename);
@ -769,7 +777,7 @@ void readDataFromFile(String filename)
mode = mstr.charAt(0);
}
#ifdef DEBUG
Serial.println("found rfid mapping for " + target + " mode " + String(mode));
Serial.print(F("found rfid mapping for ")); Serial.print(target); Serial.print(F(" mode ")); Serial.println(mode);
#endif
// Add key-value pair to the map
rfid_map[key] = MappingEntry(target, mode);
@ -943,7 +951,7 @@ void stop()
{
if (audio.isRunning())
{
Serial.println("stopping audio.");
Serial.println(F("stopping audio."));
audio.stopSong();
if (currentNode != NULL)
{
@ -1003,14 +1011,14 @@ void previous()
return;
}
Serial.print("previous(): Current song: ");
Serial.print(F("previous(): Current song: "));
Serial.println(currentSong);
// Use audio library's current time instead of tracked seconds for more accuracy
uint32_t currentAudioTime = audio.getAudioCurrentTime();
Serial.print("previous(): Current audio time: ");
Serial.print(F("previous(): Current audio time: "));
Serial.print(currentAudioTime);
Serial.println(" seconds");
Serial.println(F(" seconds"));
// Try to go to previous within current directory first
DirectoryNode *newNode = currentNode->goToPreviousMP3(2); // Use 2 second threshold
@ -1023,14 +1031,14 @@ void previous()
if (currentSong == newSong && currentAudioTime > 2)
{
// Restart current song if it's been playing for more than 2 seconds
Serial.println("previous(): Restarting current song");
Serial.println(F("previous(): Restarting current song"));
audio.setAudioPlayPosition(0);
currentNode->setSecondsPlayed(0);
}
else if (currentSong != newSong)
{
// Move to previous song in same directory
Serial.print("previous(): Moving to previous song in directory: ");
Serial.print(F("previous(): Moving to previous song in directory: "));
Serial.println(newSong);
currentNode = newNode;
stop();
@ -1079,7 +1087,7 @@ void previous()
void audio_eof_mp3(const char *info)
{
Serial.println("audio file ended.");
Serial.println(F("audio file ended."));
if (prepareSleepMode)
return;
@ -1169,7 +1177,7 @@ void readRFID()
stop();
lastUid = newUid;
Serial.print("Card UID: ");
Serial.print(F("Card UID: "));
Serial.println(lastUid);
// rfid.PICC_DumpDetailsToSerial(&(rfid.uid));
@ -1278,7 +1286,7 @@ void init_webserver() {
webreq_enter();
request->onDisconnect([](){ webreq_exit(); });
// Stream the response directly from the directory tree to avoid large temporary Strings
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, 512);
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, buffer_size);
#ifdef DEBUG
Serial.printf("Serving /directory heap=%u webreq_cnt=%u numOfFiles=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt, rootNode.getNumOfFiles());
#endif
@ -1294,7 +1302,7 @@ void init_webserver() {
webreq_enter();
request->onDisconnect([](){ webreq_exit();});
// Stream mapping to avoid building a large HTML String
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, 512);
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, buffer_size);
#ifdef DEBUG
Serial.printf("Serving /mapping heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
#endif
@ -1324,35 +1332,35 @@ void init_webserver() {
{
webreq_enter();
request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "start");
request->send_P(200, txt_plain, PSTR("start"));
start(); });
server.on("/toggleplaypause", HTTP_GET, [](AsyncWebServerRequest *request)
{
webreq_enter();
request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "toggleplaypause");
request->send_P(200, txt_plain, PSTR("toggleplaypause"));
togglePlayPause(); });
server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request)
{
webreq_enter();
request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "stop");
request->send_P(200, txt_plain, PSTR("stop"));
stop(); });
server.on("/next", HTTP_GET, [](AsyncWebServerRequest *request)
{
webreq_enter();
request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "next");
request->send_P(200, txt_plain, PSTR("next"));
next(); });
server.on("/previous", HTTP_GET, [](AsyncWebServerRequest *request)
{
webreq_enter();
request->onDisconnect([](){ webreq_exit(); });
request->send(200, txt_plain, "previous");
request->send_P(200, txt_plain, PSTR("previous"));
previous(); });
server.on("/playbyid", HTTP_GET, id_song_action);
@ -1434,14 +1442,14 @@ void setup()
if (continuePlaying)
{
Serial.print("deleting ");
Serial.print(F("deleting "));
Serial.println(progressPath);
SD.remove(progressPath);
}
deactivateSD();
activateRFID();
Serial.println("RFID");
Serial.println(F("RFID"));
// Init MFRC522
// Init SPI bus
@ -1466,7 +1474,7 @@ void setup()
// Optimize audio buffer size to save memory (ESP32-audioI2S optimization)
audio.setBufferSize(8192); // Reduced from default large buffer (saves 40-600KB!)
Serial.println("Audio init");
Serial.println(F("Audio init"));
lastVoltage = getBatteryVoltageMv();
@ -1475,7 +1483,7 @@ void setup()
AsyncWiFiManager wifiManager(&server, &dns);
// Memory optimizations for WiFiManager
wifiManager.setDebugOutput(true); // Disable debug strings
wifiManager.setDebugOutput(false); // Disable debug strings
// Reduce timeouts to free memory faster
wifiManager.setTimeout(180); // Reduced from 180
@ -1493,7 +1501,7 @@ void setup()
{
init_webserver();
server.begin();
Serial.println("Wifi init");
Serial.println(F("Wifi init"));
}
else
{
@ -1513,7 +1521,7 @@ void setup()
0); /* Core where the task should run */
lastInteraction = millis();
Serial.println("Init done.");
Serial.println(F("Init done."));
}
void id_song_action(AsyncWebServerRequest *request)
@ -1530,7 +1538,7 @@ void id_song_action(AsyncWebServerRequest *request)
}
}
lastInteraction = millis();
request->send_P(200, txt_plain, "ok");
request->send_P(200, txt_plain, PSTR("ok"));
}
void progress_action(AsyncWebServerRequest *request)
@ -1548,7 +1556,7 @@ void progress_action(AsyncWebServerRequest *request)
}
}
lastInteraction = millis();
request->send_P(200, txt_plain, "ok");
request->send_P(200, txt_plain, PSTR("ok"));
}
void volume_action(AsyncWebServerRequest *request)
@ -1566,7 +1574,7 @@ void volume_action(AsyncWebServerRequest *request)
}
}
lastInteraction = millis();
request->send_P(200, txt_plain, "ok");
request->send_P(200, txt_plain, PSTR("ok"));
}
const String getSysDir(const String filename)
@ -1760,9 +1768,9 @@ void loop()
{
if (voltage_threshold_counter > 3)
{
Serial.print("deep sleep due to low volts (");
Serial.print(F("deep sleep due to low volts ("));
Serial.print(lastVoltage);
Serial.print(") min: ");
Serial.print(F(") min: "));
Serial.println(config.minVoltage);
lastInteraction = millis() - config.sleepMessageDelay;
@ -1833,7 +1841,7 @@ void loop2(void *parameter)
}
if (!loggingDone)
{
Serial.println("loop2 started");
Serial.println(F("loop2 started"));
loggingDone = true;
}
vTaskDelay(1);