[ai] memory optimizations
This commit is contained in:
parent
b97eb79b91
commit
c14624ef92
|
|
@ -1,6 +1,10 @@
|
|||
#include "DirectoryNode.h"
|
||||
#include "globals.h"
|
||||
#include <algorithm>
|
||||
#include <cstring> // strlen, strlcpy, strlcat
|
||||
#include <strings.h> // strcasecmp
|
||||
|
||||
char DirectoryNode::buffer[DirectoryNode::buffer_size];
|
||||
|
||||
DirectoryNode::DirectoryNode(const String &nodeName)
|
||||
: name(nodeName), currentPlaying("")
|
||||
|
|
@ -42,14 +46,7 @@ const String &DirectoryNode::getDirPath() const
|
|||
return dirPath;
|
||||
}
|
||||
|
||||
String DirectoryNode::getFullPathByIndex(size_t index) const
|
||||
{
|
||||
if (index < mp3Files.size())
|
||||
{
|
||||
return buildFullPath(mp3Files[index]);
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
|
||||
String DirectoryNode::buildFullPath(const String &fileName) const
|
||||
{
|
||||
|
|
@ -65,11 +62,49 @@ String DirectoryNode::buildFullPath(const String &fileName) const
|
|||
return p;
|
||||
}
|
||||
|
||||
bool DirectoryNode::comparePathWithString(const char* path, const String& target) const
|
||||
{
|
||||
// Convert target to char* for comparison
|
||||
const char* targetStr = target.c_str();
|
||||
|
||||
// Case-insensitive string comparison
|
||||
return strcasecmp(path, targetStr) == 0;
|
||||
}
|
||||
|
||||
void DirectoryNode::buildFullPath(const String &fileName, char* out, size_t n) const
|
||||
{
|
||||
if (n == 0) return;
|
||||
out[0] = '\0';
|
||||
|
||||
if (dirPath == "/")
|
||||
{
|
||||
strlcat(out, "/", n);
|
||||
}
|
||||
else
|
||||
{
|
||||
strlcpy(out, dirPath.c_str(), n);
|
||||
strlcat(out, "/", n);
|
||||
}
|
||||
|
||||
strlcat(out, fileName.c_str(), n);
|
||||
}
|
||||
|
||||
void DirectoryNode::setCurrentPlaying(const String &mp3File)
|
||||
{
|
||||
bool isAbs = (mp3File.length() > 0) && (mp3File.charAt(0) == '/');
|
||||
const String &fileName = isAbs ? mp3File.substring(mp3File.lastIndexOf('/') + 1) : mp3File;
|
||||
currentPlaying = isAbs ? mp3File : buildFullPath(fileName);
|
||||
|
||||
if (isAbs)
|
||||
{
|
||||
currentPlaying = mp3File; // Already absolute path
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use buffer for building relative path
|
||||
buildFullPath(fileName, buffer, buffer_size);
|
||||
currentPlaying = String(buffer); // Convert back to String for assignment
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mp3Files.size(); i++)
|
||||
{
|
||||
if (mp3Files[i] == fileName && ids.size() > i)
|
||||
|
|
@ -128,9 +163,6 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
|
|||
subdirectories.clear();
|
||||
mp3Files.clear();
|
||||
ids.clear();
|
||||
subdirectories.shrink_to_fit();
|
||||
mp3Files.shrink_to_fit();
|
||||
ids.shrink_to_fit();
|
||||
|
||||
// Set directory path for this node (normalize: keep "/" or remove trailing slash)
|
||||
String path = String(currentPath);
|
||||
|
|
@ -145,6 +177,12 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
|
|||
std::vector<String> fileNames;
|
||||
|
||||
File rootDir = SD.open(currentPath);
|
||||
if (!rootDir)
|
||||
{
|
||||
Serial.print(F("buildDirectoryTree: failed to open path: "));
|
||||
Serial.println(currentPath);
|
||||
return;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
File entry = rootDir.openNextFile();
|
||||
|
|
@ -153,7 +191,7 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
|
|||
break;
|
||||
}
|
||||
|
||||
if (entry.isDirectory() && entry.name()[0] != '.' && strcmp(entry.name(), sys_dir))
|
||||
if (entry.isDirectory() && entry.name()[0] != '.' && strcmp(entry.name(), sys_dir) != 0)
|
||||
{
|
||||
dirNames.emplace_back(entry.name());
|
||||
}
|
||||
|
|
@ -195,12 +233,26 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
|
|||
for (const String &dirName : dirNames)
|
||||
{
|
||||
DirectoryNode *newNode = new DirectoryNode(dirName);
|
||||
if (!newNode)
|
||||
{
|
||||
Serial.println(F("buildDirectoryTree: OOM creating DirectoryNode"));
|
||||
continue;
|
||||
}
|
||||
subdirectories.push_back(newNode);
|
||||
|
||||
String childPath = String(currentPath);
|
||||
if (!childPath.endsWith("/"))
|
||||
String childPath;
|
||||
childPath.reserve(dirPath.length() + 1 + dirName.length());
|
||||
if (dirPath == "/")
|
||||
{
|
||||
childPath = "/";
|
||||
childPath += dirName;
|
||||
}
|
||||
else
|
||||
{
|
||||
childPath = dirPath;
|
||||
childPath += "/";
|
||||
childPath += dirName;
|
||||
childPath += dirName;
|
||||
}
|
||||
|
||||
newNode->buildDirectoryTree(childPath.c_str());
|
||||
}
|
||||
|
|
@ -220,16 +272,19 @@ void DirectoryNode::printDirectoryTree(int level) const
|
|||
Serial.print(F(" "));
|
||||
}
|
||||
Serial.println(name);
|
||||
|
||||
|
||||
for (const String &mp3File : mp3Files)
|
||||
{
|
||||
for (int i = 0; i <= level; i++)
|
||||
{
|
||||
Serial.print(F(" "));
|
||||
}
|
||||
Serial.println(buildFullPath(mp3File));
|
||||
|
||||
// Use buffer for building path
|
||||
buildFullPath(mp3File, buffer, buffer_size);
|
||||
Serial.println(buffer);
|
||||
}
|
||||
|
||||
|
||||
for (DirectoryNode *childNode : subdirectories)
|
||||
{
|
||||
childNode->printDirectoryTree(level + 1);
|
||||
|
|
@ -274,7 +329,8 @@ DirectoryNode *DirectoryNode::advanceToMP3(const uint16_t id)
|
|||
if (id == ids[i])
|
||||
{
|
||||
// Found the current MP3 file
|
||||
currentPlaying = buildFullPath(mp3Files[i]);
|
||||
buildFullPath(mp3Files[i], buffer, buffer_size);
|
||||
currentPlaying = String(buffer); // Convert back to String for assignment
|
||||
currentPlayingId = id;
|
||||
return this;
|
||||
}
|
||||
|
|
@ -329,7 +385,9 @@ DirectoryNode *DirectoryNode::advanceToMP3(const String &songName)
|
|||
{
|
||||
if (isAbsolutePath)
|
||||
{
|
||||
if (buildFullPath(mp3Files[i]).equalsIgnoreCase(songName))
|
||||
// Use static buffer for path building and comparison
|
||||
buildFullPath(mp3Files[i], buffer, buffer_size);
|
||||
if (comparePathWithString(buffer, songName))
|
||||
{
|
||||
setCurrentPlaying(mp3Files[i]);
|
||||
return this;
|
||||
|
|
@ -337,7 +395,9 @@ DirectoryNode *DirectoryNode::advanceToMP3(const String &songName)
|
|||
}
|
||||
else
|
||||
{
|
||||
String f = mp3Files[i];
|
||||
// Use static buffer for case conversion and comparison
|
||||
buildFullPath(mp3Files[i], buffer, buffer_size);
|
||||
String f = String(buffer);
|
||||
f.toLowerCase();
|
||||
if (f.endsWith(lowTarget))
|
||||
{
|
||||
|
|
@ -431,7 +491,8 @@ DirectoryNode *DirectoryNode::goToPreviousMP3(uint32_t thresholdSeconds)
|
|||
int currentIndex = -1;
|
||||
for (size_t i = 0; i < mp3Files.size(); i++)
|
||||
{
|
||||
if (currentPlaying == buildFullPath(mp3Files[i]))
|
||||
buildFullPath(mp3Files[i], buffer, buffer_size);
|
||||
if (comparePathWithString(buffer, currentPlaying))
|
||||
{
|
||||
currentIndex = i;
|
||||
break;
|
||||
|
|
@ -442,7 +503,7 @@ DirectoryNode *DirectoryNode::goToPreviousMP3(uint32_t thresholdSeconds)
|
|||
if (currentIndex > 0)
|
||||
{
|
||||
Serial.print(F("goToPreviousMP3: Moving to previous song in same directory: "));
|
||||
Serial.println(buildFullPath(mp3Files[currentIndex - 1]));
|
||||
Serial.println(mp3Files[currentIndex - 1]);
|
||||
setCurrentPlaying(mp3Files[currentIndex - 1]);
|
||||
return this;
|
||||
}
|
||||
|
|
@ -471,7 +532,9 @@ DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String ¤tGloba
|
|||
{
|
||||
DirectoryNode *node = allMP3s[i].first;
|
||||
int fileIndex = allMP3s[i].second;
|
||||
if (node->buildFullPath(node->mp3Files[fileIndex]) == currentGlobal)
|
||||
|
||||
node->buildFullPath(node->mp3Files[fileIndex], buffer, buffer_size);
|
||||
if (comparePathWithString(buffer, currentGlobal))
|
||||
{
|
||||
currentGlobalIndex = i;
|
||||
break;
|
||||
|
|
@ -483,10 +546,12 @@ DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String ¤tGloba
|
|||
{
|
||||
DirectoryNode *prevNode = allMP3s[currentGlobalIndex - 1].first;
|
||||
int prevFileIndex = allMP3s[currentGlobalIndex - 1].second;
|
||||
|
||||
|
||||
prevNode->buildFullPath(prevNode->mp3Files[prevFileIndex], buffer, buffer_size);
|
||||
|
||||
Serial.print(F("findPreviousMP3Globally: Moving to previous song globally: "));
|
||||
Serial.println(prevNode->buildFullPath(prevNode->mp3Files[prevFileIndex]));
|
||||
|
||||
Serial.println(buffer);
|
||||
|
||||
prevNode->setCurrentPlaying(prevNode->mp3Files[prevFileIndex]);
|
||||
return prevNode;
|
||||
}
|
||||
|
|
@ -525,7 +590,8 @@ DirectoryNode *DirectoryNode::advanceToNextMP3(const String ¤tGlobal)
|
|||
{
|
||||
for (size_t i = 0; i < mp3Files.size(); i++)
|
||||
{
|
||||
if (currentGlobal == buildFullPath(mp3Files[i]))
|
||||
buildFullPath(mp3Files[i], buffer, buffer_size);
|
||||
if (currentGlobal == String(buffer))
|
||||
{
|
||||
// Found the current playing MP3 file
|
||||
if (i < mp3Files.size() - 1)
|
||||
|
|
@ -555,7 +621,8 @@ DirectoryNode *DirectoryNode::advanceToNextMP3(const String ¤tGlobal)
|
|||
// Have each subdirectory advance its song
|
||||
for (size_t i = 0; i < subdir->mp3Files.size(); i++)
|
||||
{
|
||||
if (currentGlobal == subdir->buildFullPath(subdir->mp3Files[i]))
|
||||
subdir->buildFullPath(subdir->mp3Files[i], buffer, buffer_size);
|
||||
if (currentGlobal == String(buffer))
|
||||
{
|
||||
// Found the current playing MP3 file
|
||||
if (i < subdir->mp3Files.size() - 1)
|
||||
|
|
@ -603,10 +670,11 @@ void DirectoryNode::streamDirectoryHTML(Print &out) const {
|
|||
out.print(F("<li data-id=\""));
|
||||
out.print(ids[i]);
|
||||
out.print(F("\">"));
|
||||
out.print(buildFullPath(mp3Files[i]));
|
||||
buildFullPath(mp3Files[i], buffer, buffer_size);
|
||||
out.print(buffer);
|
||||
out.println(F("</li>"));
|
||||
#ifdef DEBUG
|
||||
Serial.printf("stream song: %s\n", buildFullPath(mp3Files[i]).c_str());
|
||||
Serial.printf("stream song: %s\n", buffer);
|
||||
#endif
|
||||
// Yield every few items to allow the async web server to send buffered data
|
||||
if (i % 5 == 4) {
|
||||
|
|
@ -614,6 +682,8 @@ void DirectoryNode::streamDirectoryHTML(Print &out) const {
|
|||
}
|
||||
}
|
||||
|
||||
out.flush();
|
||||
|
||||
for (DirectoryNode* child : subdirectories) {
|
||||
child->streamDirectoryHTML(out);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,17 @@ private:
|
|||
std::vector<DirectoryNode*> subdirectories;
|
||||
std::vector<String> mp3Files;
|
||||
std::vector<uint16_t> ids;
|
||||
static const size_t path_size = 256;
|
||||
String currentPlaying;
|
||||
uint16_t currentPlayingId = 0;
|
||||
uint16_t secondsPlayed = 0;
|
||||
static const size_t buffer_size = path_size;
|
||||
static char buffer[buffer_size];
|
||||
|
||||
String buildFullPath(const String& fileName) const;
|
||||
void buildFullPath(const String &fileName, char* buffer, size_t bufferSize) const;
|
||||
bool comparePathWithString(const char* path, const String& target) const;
|
||||
|
||||
|
||||
|
||||
public:
|
||||
|
|
@ -36,7 +42,6 @@ public:
|
|||
const std::vector<DirectoryNode*>& getSubdirectories() const;
|
||||
const std::vector<String>& getMP3Files() const;
|
||||
const String& getDirPath() const;
|
||||
String getFullPathByIndex(size_t index) const;
|
||||
|
||||
size_t getNumOfFiles() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ static const char* hdr_connection_key = "Connection";
|
|||
|
||||
static const char* hdr_connection_val = "close";
|
||||
|
||||
const size_t buffer_size = 256;
|
||||
const size_t buffer_size = 80;
|
||||
|
||||
|
||||
/*
|
||||
|
|
|
|||
160
src/main.cpp
160
src/main.cpp
|
|
@ -28,6 +28,39 @@
|
|||
// webrequest_blockings is a simple watchdog counter that tracks how long at least one HTTP request has been “active” (not yet disconnected) according to the AsyncWebServer.
|
||||
int webrequest_blockings = 0;
|
||||
|
||||
// Prebuilt system paths to avoid repeated String allocations
|
||||
static String SYS_PREFIX;
|
||||
static String PATH_INDEX, PATH_INDEX_GZ, PATH_STYLE, PATH_STYLE_GZ, PATH_SCRIPT, PATH_SCRIPT_GZ;
|
||||
static String PATH_MAPPING, PATH_PROGRESS, PATH_CONFIG, PATH_SLEEP, PATH_STARTUP;
|
||||
|
||||
static inline void buildSystemPathsOnce() {
|
||||
if (!SYS_PREFIX.isEmpty()) return;
|
||||
|
||||
SYS_PREFIX = "/";
|
||||
SYS_PREFIX += sys_dir;
|
||||
SYS_PREFIX += "/";
|
||||
|
||||
auto make = [](const char* name) {
|
||||
String s;
|
||||
s = SYS_PREFIX;
|
||||
s += name;
|
||||
return s;
|
||||
};
|
||||
|
||||
PATH_INDEX = make(index_file);
|
||||
PATH_STYLE = make(style_file);
|
||||
PATH_SCRIPT = make(script_file);
|
||||
PATH_MAPPING = make(mapping_file);
|
||||
PATH_PROGRESS= make(progress_file);
|
||||
PATH_CONFIG = make(config_file);
|
||||
PATH_SLEEP = make(sleep_sound);
|
||||
PATH_STARTUP = make(startup_sound);
|
||||
|
||||
PATH_INDEX_GZ = PATH_INDEX; PATH_INDEX_GZ += F(".gz");
|
||||
PATH_STYLE_GZ = PATH_STYLE; PATH_STYLE_GZ += F(".gz");
|
||||
PATH_SCRIPT_GZ = PATH_SCRIPT; PATH_SCRIPT_GZ += F(".gz");
|
||||
}
|
||||
|
||||
|
||||
void activateSD()
|
||||
{
|
||||
|
|
@ -118,27 +151,7 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
|
|||
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 = "/";
|
||||
filepath += baseName;
|
||||
filepath += "_";
|
||||
filepath += counter;
|
||||
filepath += extension;
|
||||
counter++;
|
||||
} while (SD.exists(filepath) && counter < 100);
|
||||
|
||||
if (counter >= 100)
|
||||
{
|
||||
request->send(409, txt_plain, F("Too many files w sim names"));
|
||||
return;
|
||||
}
|
||||
Serial.print(F("File exists, using: "));
|
||||
Serial.println(filepath);
|
||||
request->send(500, txt_plain, F("File already exists."));
|
||||
}
|
||||
|
||||
// Open the file for writing (guard with simple mutex)
|
||||
|
|
@ -266,9 +279,6 @@ uint32_t getBatteryVoltageMv()
|
|||
{
|
||||
uint32_t voltage = analogReadMilliVolts(BAT_VOLTAGE_PIN);
|
||||
voltage *= 2; //*2 because of the voltage divider.
|
||||
// Serial.print("Battery Voltage: ");
|
||||
// Serial.println(voltage);
|
||||
// Serial.println(" mV");
|
||||
return voltage;
|
||||
}
|
||||
|
||||
|
|
@ -735,7 +745,7 @@ void editMapping(AsyncWebServerRequest *request)
|
|||
}
|
||||
|
||||
rfid_map[rfid] = MappingEntry(song, mode);
|
||||
saveMappingToFile(getSysDir(mapping_file));
|
||||
saveMappingToFile(PATH_MAPPING);
|
||||
request->send_P(200, txt_plain, PSTR("Mapping updated"));
|
||||
}
|
||||
else
|
||||
|
|
@ -901,6 +911,7 @@ static void streamMappingHTML(Print &out)
|
|||
out.print(pair.second.mode);
|
||||
out.print(F("</td></tr>"));
|
||||
// Yield occasionally if async server is buffering
|
||||
out.flush();
|
||||
yield();
|
||||
}
|
||||
out.print(F("</table>"));
|
||||
|
|
@ -975,7 +986,7 @@ void togglePlayPause()
|
|||
{
|
||||
if (currentNode != NULL)
|
||||
{
|
||||
writeSongProgress(getSysDir(progress_file).c_str(), currentNode->getCurrentPlayingId(), currentNode->getSecondsPlayed());
|
||||
writeSongProgress(PATH_PROGRESS.c_str(), currentNode->getCurrentPlayingId(), currentNode->getSecondsPlayed());
|
||||
audio.pauseResume();
|
||||
}
|
||||
else
|
||||
|
|
@ -1247,37 +1258,22 @@ static void serveStaticFile(AsyncWebServerRequest *request,
|
|||
}
|
||||
|
||||
void init_webserver() {
|
||||
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
static String htmlPath = "";
|
||||
static String htmlPathGz = "";
|
||||
if (htmlPath.isEmpty()) {
|
||||
htmlPath = getSysDir(index_file);
|
||||
htmlPathGz = htmlPath + F(".gz");
|
||||
}
|
||||
serveStaticFile(request, htmlPath, htmlPathGz, txt_html_charset, hdr_cache_control_val, F("ERROR: /system/index.html(.gz) not found!"), true);
|
||||
serveStaticFile(request, PATH_INDEX, PATH_INDEX_GZ, txt_html_charset, hdr_cache_control_val, F("ERROR: /system/index.html(.gz) not found!"), true);
|
||||
|
||||
|
||||
});
|
||||
|
||||
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
static String cssPath = "";
|
||||
static String cssPathGz = "";
|
||||
if (cssPath.isEmpty()) {
|
||||
cssPath = getSysDir(style_file);
|
||||
cssPathGz = cssPath + F(".gz");
|
||||
}
|
||||
serveStaticFile(request, cssPath, cssPathGz, "text/css", "public, max-age=300", F("ERROR: /system/style.css(.gz) not found!"), true);
|
||||
serveStaticFile(request, PATH_STYLE, PATH_STYLE_GZ, "text/css", "public, max-age=300", F("ERROR: /system/style.css(.gz) not found!"), true);
|
||||
});
|
||||
|
||||
server.on("/script.js", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
static String jsPath = "";
|
||||
static String jsPathGz = "";
|
||||
if (jsPath.isEmpty()) {
|
||||
jsPath = getSysDir(script_file);
|
||||
jsPathGz = jsPath + F(".gz");
|
||||
}
|
||||
serveStaticFile(request, jsPath, jsPathGz, "application/javascript", "public, max-age=300", F("ERROR: /system/script.js(.gz) not found!"), true);
|
||||
serveStaticFile(request, PATH_SCRIPT, PATH_SCRIPT_GZ, "application/javascript", "public, max-age=300", F("ERROR: /system/script.js(.gz) not found!"), true);
|
||||
});
|
||||
|
||||
// Dynamic endpoints to avoid template processing heap spikes
|
||||
|
|
@ -1287,9 +1283,7 @@ void init_webserver() {
|
|||
request->onDisconnect([](){ webreq_exit(); });
|
||||
// Stream the response directly from the directory tree to avoid large temporary Strings
|
||||
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
|
||||
Serial.printf("Serving /directory heap=%u webreq_cnt=%u numOfFiles=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt, rootNode.getNumOfFiles());
|
||||
stream->addHeader(hdr_cache_control_key, hdr_cache_control_val);
|
||||
stream->addHeader(hdr_connection_key, hdr_connection_val);
|
||||
// Generate HTML directly into the stream under lock
|
||||
|
|
@ -1303,7 +1297,7 @@ void init_webserver() {
|
|||
request->onDisconnect([](){ webreq_exit();});
|
||||
// Stream mapping to avoid building a large HTML String
|
||||
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, buffer_size);
|
||||
#ifdef DEBUG
|
||||
#ifdef DEBUG
|
||||
Serial.printf("Serving /mapping heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
|
||||
#endif
|
||||
stream->addHeader(hdr_cache_control_key, hdr_cache_control_val);
|
||||
|
|
@ -1317,11 +1311,11 @@ void init_webserver() {
|
|||
{
|
||||
webreq_enter();
|
||||
request->onDisconnect([](){ webreq_exit(); });
|
||||
#ifdef DEBUG
|
||||
#ifdef DEBUG
|
||||
Serial.printf("Serving /state heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
|
||||
#endif
|
||||
// Stream JSON directly to avoid DynamicJsonDocument/String allocations
|
||||
AsyncResponseStream* stream = request->beginResponseStream(F("application/json; charset=UTF-8"), 256);
|
||||
AsyncResponseStream* stream = request->beginResponseStream(F("application/json; charset=UTF-8"), buffer_size);
|
||||
stream->addHeader(hdr_cache_control_key, hdr_cache_control_val);
|
||||
stream->addHeader(hdr_connection_key, hdr_connection_val);
|
||||
streamStateJSON(*stream);
|
||||
|
|
@ -1416,6 +1410,7 @@ void setup()
|
|||
Serial.print(F("Initializing SD card..."));
|
||||
activateSD();
|
||||
Serial.println(F("SD initialization done."));
|
||||
buildSystemPathsOnce();
|
||||
|
||||
// Seed RNG for shuffle mode
|
||||
#if defined(ESP32)
|
||||
|
|
@ -1433,10 +1428,11 @@ void setup()
|
|||
|
||||
rootNode.buildDirectoryTree("/");
|
||||
rootNode.printDirectoryTree();
|
||||
Serial.printf("Heap after dir tree: %u\n", (unsigned)xPortGetFreeHeapSize());
|
||||
|
||||
readDataFromFile(getSysDir(mapping_file));
|
||||
readDataFromFile(PATH_MAPPING);
|
||||
|
||||
String progressPath = getSysDir(progress_file);
|
||||
String progressPath = PATH_PROGRESS;
|
||||
|
||||
continuePlaying = config.startAtStoredProgress && readSongProgress(progressPath.c_str());
|
||||
|
||||
|
|
@ -1471,8 +1467,8 @@ void setup()
|
|||
audio.setVolume(config.initialVolume); // Use config value
|
||||
volume = config.initialVolume; // Update global volume variable
|
||||
|
||||
// Optimize audio buffer size to save memory (ESP32-audioI2S optimization)
|
||||
audio.setBufferSize(8192); // Reduced from default large buffer (saves 40-600KB!)
|
||||
// Optimize audio buffer size to save heap (lower = less RAM, but risk of underflow on high bitrates)
|
||||
audio.setBufferSize(8000);
|
||||
|
||||
Serial.println(F("Audio init"));
|
||||
|
||||
|
|
@ -1499,8 +1495,11 @@ void setup()
|
|||
|
||||
if (wifiManager.autoConnect("HannaBox"))
|
||||
{
|
||||
Serial.printf("Heap before init_webserver: %u\n", (unsigned)xPortGetFreeHeapSize());
|
||||
init_webserver();
|
||||
Serial.printf("Heap before server.begin: %u\n", (unsigned)xPortGetFreeHeapSize());
|
||||
server.begin();
|
||||
Serial.printf("Heap after server.begin: %u\n", (unsigned)xPortGetFreeHeapSize());
|
||||
Serial.println(F("Wifi init"));
|
||||
}
|
||||
else
|
||||
|
|
@ -1514,7 +1513,7 @@ void setup()
|
|||
xTaskCreatePinnedToCore(
|
||||
loop2, /* Function to implement the task */
|
||||
"RFIDTask", /* Name of the task */
|
||||
4096, /* Stack size in words - reduced from 10000 to 4096 (optimization 2) */
|
||||
2048, /* Stack size in words - reduced from 4096 to 2048 to free heap */
|
||||
NULL, /* Task input parameter */
|
||||
0, /* Priority of the task */
|
||||
&RfidTask, /* Task handle. */
|
||||
|
|
@ -1577,25 +1576,15 @@ void volume_action(AsyncWebServerRequest *request)
|
|||
request->send_P(200, txt_plain, PSTR("ok"));
|
||||
}
|
||||
|
||||
const String getSysDir(const String filename)
|
||||
{
|
||||
String st_sys_str(96);
|
||||
st_sys_str.clear();
|
||||
st_sys_str.concat("/");
|
||||
st_sys_str.concat(sys_dir);
|
||||
st_sys_str.concat("/");
|
||||
st_sys_str.concat(filename);
|
||||
return st_sys_str;
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (webreq_cnt > 0 && webrequest_blockings > MAX_WEBREQUEST_BLOCKINGS) {
|
||||
Serial.println(F("excessive webrequest blocking - suppress reset"));
|
||||
// Avoid resetting server mid-response to prevent mixing headers/body or truncation
|
||||
server.reset();
|
||||
init_webserver();
|
||||
webreq_cnt = 0;
|
||||
if (!server_reset_pending) {
|
||||
Serial.println(F("excessive webrequest blocking - scheduling server reset"));
|
||||
server_reset_pending = true;
|
||||
}
|
||||
// reset the counter to avoid repeated scheduling while still busy
|
||||
webrequest_blockings = 0;
|
||||
}
|
||||
|
||||
|
|
@ -1638,7 +1627,7 @@ void loop()
|
|||
else if (!startupSoundPlayed)
|
||||
{
|
||||
startupSoundPlayed = true;
|
||||
playSongByPath(getSysDir(startup_sound));
|
||||
playSongByPath(PATH_STARTUP);
|
||||
}
|
||||
|
||||
// send device to sleep:
|
||||
|
|
@ -1650,18 +1639,12 @@ void loop()
|
|||
prepareSleepMode = true;
|
||||
if (currentNode != nullptr)
|
||||
{
|
||||
static String progressPath = "";
|
||||
if (progressPath.isEmpty()) {
|
||||
progressPath = getSysDir(progress_file);
|
||||
}
|
||||
|
||||
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
writeSongProgress(progressPath.c_str(), currentNode->getCurrentPlayingId(), currentNode->getSecondsPlayed());
|
||||
writeSongProgress(PATH_PROGRESS.c_str(), currentNode->getCurrentPlayingId(), currentNode->getSecondsPlayed());
|
||||
}
|
||||
|
||||
playSongByPath(getSysDir(sleep_sound));
|
||||
playSongByPath(PATH_SLEEP);
|
||||
}
|
||||
|
||||
if (now - lastInteraction > config.sleepDelay)
|
||||
|
|
@ -1708,7 +1691,7 @@ void loop()
|
|||
}
|
||||
audio.setVolume(vol);
|
||||
volume = vol;
|
||||
playSongByPath(getSysDir(startup_sound));
|
||||
playSongByPath(PATH_STARTUP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1743,7 +1726,7 @@ void loop()
|
|||
}
|
||||
audio.setVolume(vol);
|
||||
volume = vol;
|
||||
playSongByPath(getSysDir(startup_sound));
|
||||
playSongByPath(PATH_STARTUP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1793,6 +1776,15 @@ void loop()
|
|||
webrequest_blockings = 0;
|
||||
}
|
||||
|
||||
// Perform deferred server reset when no active requests to avoid AsyncTCP stack corruption
|
||||
if (server_reset_pending && webreq_cnt == 0) {
|
||||
Serial.println(F("performing deferred server reset"));
|
||||
server.reset();
|
||||
init_webserver();
|
||||
server.begin();
|
||||
server_reset_pending = false;
|
||||
}
|
||||
|
||||
loopCounter++;
|
||||
vTaskDelay(1);
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ bool RFIDActive = false;
|
|||
volatile uint32_t webreq_cnt = 0;
|
||||
static inline void webreq_enter() { __sync_add_and_fetch(&webreq_cnt, 1); }
|
||||
static inline void webreq_exit() { __sync_sub_and_fetch(&webreq_cnt, 1); }
|
||||
volatile bool server_reset_pending = false;
|
||||
|
||||
uint16_t voltage_threshold_counter = 0;
|
||||
|
||||
|
|
@ -111,7 +112,6 @@ void init_webserver();
|
|||
|
||||
boolean buttonPressed(const uint8_t pin);
|
||||
|
||||
const String getSysDir(const String filename);
|
||||
|
||||
/**
|
||||
* Helper routine to dump a byte array as hex values to Serial.
|
||||
|
|
|
|||
Loading…
Reference in New Issue