[ai] further memory optimizations
This commit is contained in:
parent
3a34b1b8d0
commit
fd40b663a0
|
|
@ -20,8 +20,9 @@ lib_deps =
|
||||||
bblanchon/ArduinoJson@^6.21.3
|
bblanchon/ArduinoJson@^6.21.3
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
build_flags =
|
build_flags =
|
||||||
-Os ; Optimize for size
|
-Os ; Optimize for size
|
||||||
; -DCORE_DEBUG_LEVEL=0 ; Disable all debug output
|
; -DDEBUG ; Hannabox Debugging
|
||||||
|
-DCORE_DEBUG_LEVEL=0 ; Disable all debug output
|
||||||
-DARDUINO_LOOP_STACK_SIZE=3072 ; Further reduce from 4096
|
-DARDUINO_LOOP_STACK_SIZE=3072 ; Further reduce from 4096
|
||||||
-DWIFI_TASK_STACK_SIZE=3072 ; Reduce WiFi task stack
|
-DWIFI_TASK_STACK_SIZE=3072 ; Reduce WiFi task stack
|
||||||
-DARDUINO_EVENT_TASK_STACK_SIZE=2048 ; Reduce event task stack
|
-DARDUINO_EVENT_TASK_STACK_SIZE=2048 ; Reduce event task stack
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,42 @@ const std::vector<String> &DirectoryNode::getMP3Files() const
|
||||||
return mp3Files;
|
return mp3Files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
if (dirPath == "/")
|
||||||
|
{
|
||||||
|
String p = "/";
|
||||||
|
p += fileName;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
String p = dirPath;
|
||||||
|
p += "/";
|
||||||
|
p += fileName;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
void DirectoryNode::setCurrentPlaying(const String &mp3File)
|
void DirectoryNode::setCurrentPlaying(const String &mp3File)
|
||||||
{
|
{
|
||||||
currentPlaying = mp3File;
|
bool isAbs = (mp3File.length() > 0) && (mp3File.charAt(0) == '/');
|
||||||
|
const String &fileName = isAbs ? mp3File.substring(mp3File.lastIndexOf('/') + 1) : mp3File;
|
||||||
|
currentPlaying = isAbs ? mp3File : buildFullPath(fileName);
|
||||||
for (size_t i = 0; i < mp3Files.size(); i++)
|
for (size_t i = 0; i < mp3Files.size(); i++)
|
||||||
{
|
{
|
||||||
if (mp3Files[i] == mp3File && ids.size() > i)
|
if (mp3Files[i] == fileName && ids.size() > i)
|
||||||
{
|
{
|
||||||
currentPlayingId = ids[i];
|
currentPlayingId = ids[i];
|
||||||
break;
|
break;
|
||||||
|
|
@ -102,6 +132,14 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
|
||||||
mp3Files.shrink_to_fit();
|
mp3Files.shrink_to_fit();
|
||||||
ids.shrink_to_fit();
|
ids.shrink_to_fit();
|
||||||
|
|
||||||
|
// Set directory path for this node (normalize: keep "/" or remove trailing slash)
|
||||||
|
String path = String(currentPath);
|
||||||
|
if (path.length() > 1 && path.endsWith("/"))
|
||||||
|
{
|
||||||
|
path.remove(path.length() - 1);
|
||||||
|
}
|
||||||
|
dirPath = path;
|
||||||
|
|
||||||
// First collect entries so we can sort them alphabetically
|
// First collect entries so we can sort them alphabetically
|
||||||
std::vector<String> dirNames;
|
std::vector<String> dirNames;
|
||||||
std::vector<String> fileNames;
|
std::vector<String> fileNames;
|
||||||
|
|
@ -167,15 +205,10 @@ void DirectoryNode::buildDirectoryTree(const char *currentPath)
|
||||||
newNode->buildDirectoryTree(childPath.c_str());
|
newNode->buildDirectoryTree(childPath.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add MP3 files in alphabetical order
|
// Add MP3 files in alphabetical order (store only filenames; build full paths on demand)
|
||||||
for (const String &fileName : fileNames)
|
for (const String &fileName : fileNames)
|
||||||
{
|
{
|
||||||
String fullPath = String(currentPath);
|
mp3Files.push_back(fileName);
|
||||||
if (!fullPath.endsWith("/"))
|
|
||||||
fullPath += "/";
|
|
||||||
fullPath += fileName;
|
|
||||||
|
|
||||||
mp3Files.push_back(std::move(fullPath));
|
|
||||||
ids.push_back(getNextId());
|
ids.push_back(getNextId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +227,7 @@ void DirectoryNode::printDirectoryTree(int level) const
|
||||||
{
|
{
|
||||||
Serial.print(F(" "));
|
Serial.print(F(" "));
|
||||||
}
|
}
|
||||||
Serial.println(mp3File);
|
Serial.println(buildFullPath(mp3File));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DirectoryNode *childNode : subdirectories)
|
for (DirectoryNode *childNode : subdirectories)
|
||||||
|
|
@ -241,7 +274,7 @@ DirectoryNode *DirectoryNode::advanceToMP3(const uint16_t id)
|
||||||
if (id == ids[i])
|
if (id == ids[i])
|
||||||
{
|
{
|
||||||
// Found the current MP3 file
|
// Found the current MP3 file
|
||||||
currentPlaying = mp3Files[i];
|
currentPlaying = buildFullPath(mp3Files[i]);
|
||||||
currentPlayingId = id;
|
currentPlayingId = id;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +329,7 @@ DirectoryNode *DirectoryNode::advanceToMP3(const String &songName)
|
||||||
{
|
{
|
||||||
if (isAbsolutePath)
|
if (isAbsolutePath)
|
||||||
{
|
{
|
||||||
if (mp3Files[i].equalsIgnoreCase(songName))
|
if (buildFullPath(mp3Files[i]).equalsIgnoreCase(songName))
|
||||||
{
|
{
|
||||||
setCurrentPlaying(mp3Files[i]);
|
setCurrentPlaying(mp3Files[i]);
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -317,13 +350,10 @@ DirectoryNode *DirectoryNode::advanceToMP3(const String &songName)
|
||||||
// Then search in subdirectories
|
// Then search in subdirectories
|
||||||
for (auto subdir : subdirectories)
|
for (auto subdir : subdirectories)
|
||||||
{
|
{
|
||||||
// Absolute folder target: match directory by its full path derived from its files
|
// Absolute folder target: match directory by its full path (dirPath)
|
||||||
if (isAbsolutePath && subdir->mp3Files.size() > 0)
|
if (isAbsolutePath)
|
||||||
{
|
{
|
||||||
String anyFile = subdir->mp3Files[0];
|
if (subdir->getDirPath().equalsIgnoreCase(normalizedPath))
|
||||||
int lastSlash = anyFile.lastIndexOf('/');
|
|
||||||
String subdirPath = (lastSlash >= 0) ? anyFile.substring(0, lastSlash) : String();
|
|
||||||
if (subdirPath.equalsIgnoreCase(normalizedPath))
|
|
||||||
{
|
{
|
||||||
subdir->advanceToFirstMP3InThisNode();
|
subdir->advanceToFirstMP3InThisNode();
|
||||||
return subdir;
|
return subdir;
|
||||||
|
|
@ -342,7 +372,7 @@ DirectoryNode *DirectoryNode::advanceToMP3(const String &songName)
|
||||||
|
|
||||||
if (isAbsolutePath)
|
if (isAbsolutePath)
|
||||||
{
|
{
|
||||||
if (subdir->mp3Files[i].equalsIgnoreCase(songName))
|
if (subdir->buildFullPath(subdir->mp3Files[i]).equalsIgnoreCase(songName))
|
||||||
{
|
{
|
||||||
subdir->setCurrentPlaying(subdir->mp3Files[i]);
|
subdir->setCurrentPlaying(subdir->mp3Files[i]);
|
||||||
return subdir;
|
return subdir;
|
||||||
|
|
@ -401,7 +431,7 @@ DirectoryNode *DirectoryNode::goToPreviousMP3(uint32_t thresholdSeconds)
|
||||||
int currentIndex = -1;
|
int currentIndex = -1;
|
||||||
for (size_t i = 0; i < mp3Files.size(); i++)
|
for (size_t i = 0; i < mp3Files.size(); i++)
|
||||||
{
|
{
|
||||||
if (currentPlaying == mp3Files[i])
|
if (currentPlaying == buildFullPath(mp3Files[i]))
|
||||||
{
|
{
|
||||||
currentIndex = i;
|
currentIndex = i;
|
||||||
break;
|
break;
|
||||||
|
|
@ -412,7 +442,7 @@ DirectoryNode *DirectoryNode::goToPreviousMP3(uint32_t thresholdSeconds)
|
||||||
if (currentIndex > 0)
|
if (currentIndex > 0)
|
||||||
{
|
{
|
||||||
Serial.print(F("goToPreviousMP3: Moving to previous song in same directory: "));
|
Serial.print(F("goToPreviousMP3: Moving to previous song in same directory: "));
|
||||||
Serial.println(mp3Files[currentIndex - 1]);
|
Serial.println(buildFullPath(mp3Files[currentIndex - 1]));
|
||||||
setCurrentPlaying(mp3Files[currentIndex - 1]);
|
setCurrentPlaying(mp3Files[currentIndex - 1]);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -441,7 +471,7 @@ DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String ¤tGloba
|
||||||
{
|
{
|
||||||
DirectoryNode *node = allMP3s[i].first;
|
DirectoryNode *node = allMP3s[i].first;
|
||||||
int fileIndex = allMP3s[i].second;
|
int fileIndex = allMP3s[i].second;
|
||||||
if (node->mp3Files[fileIndex] == currentGlobal)
|
if (node->buildFullPath(node->mp3Files[fileIndex]) == currentGlobal)
|
||||||
{
|
{
|
||||||
currentGlobalIndex = i;
|
currentGlobalIndex = i;
|
||||||
break;
|
break;
|
||||||
|
|
@ -455,7 +485,7 @@ DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String ¤tGloba
|
||||||
int prevFileIndex = allMP3s[currentGlobalIndex - 1].second;
|
int prevFileIndex = allMP3s[currentGlobalIndex - 1].second;
|
||||||
|
|
||||||
Serial.print(F("findPreviousMP3Globally: Moving to previous song globally: "));
|
Serial.print(F("findPreviousMP3Globally: Moving to previous song globally: "));
|
||||||
Serial.println(prevNode->mp3Files[prevFileIndex]);
|
Serial.println(prevNode->buildFullPath(prevNode->mp3Files[prevFileIndex]));
|
||||||
|
|
||||||
prevNode->setCurrentPlaying(prevNode->mp3Files[prevFileIndex]);
|
prevNode->setCurrentPlaying(prevNode->mp3Files[prevFileIndex]);
|
||||||
return prevNode;
|
return prevNode;
|
||||||
|
|
@ -495,7 +525,7 @@ DirectoryNode *DirectoryNode::advanceToNextMP3(const String ¤tGlobal)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < mp3Files.size(); i++)
|
for (size_t i = 0; i < mp3Files.size(); i++)
|
||||||
{
|
{
|
||||||
if (currentGlobal == mp3Files[i])
|
if (currentGlobal == buildFullPath(mp3Files[i]))
|
||||||
{
|
{
|
||||||
// Found the current playing MP3 file
|
// Found the current playing MP3 file
|
||||||
if (i < mp3Files.size() - 1)
|
if (i < mp3Files.size() - 1)
|
||||||
|
|
@ -525,7 +555,7 @@ DirectoryNode *DirectoryNode::advanceToNextMP3(const String ¤tGlobal)
|
||||||
// Have each subdirectory advance its song
|
// Have each subdirectory advance its song
|
||||||
for (size_t i = 0; i < subdir->mp3Files.size(); i++)
|
for (size_t i = 0; i < subdir->mp3Files.size(); i++)
|
||||||
{
|
{
|
||||||
if (currentGlobal == subdir->mp3Files[i])
|
if (currentGlobal == subdir->buildFullPath(subdir->mp3Files[i]))
|
||||||
{
|
{
|
||||||
// Found the current playing MP3 file
|
// Found the current playing MP3 file
|
||||||
if (i < subdir->mp3Files.size() - 1)
|
if (i < subdir->mp3Files.size() - 1)
|
||||||
|
|
@ -573,10 +603,10 @@ void DirectoryNode::streamDirectoryHTML(Print &out) const {
|
||||||
out.print(F("<li data-id=\""));
|
out.print(F("<li data-id=\""));
|
||||||
out.print(ids[i]);
|
out.print(ids[i]);
|
||||||
out.print(F("\">"));
|
out.print(F("\">"));
|
||||||
out.print(mp3Files[i]);
|
out.print(buildFullPath(mp3Files[i]));
|
||||||
out.println(F("</li>"));
|
out.println(F("</li>"));
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
Serial.printf("stream song: %s\n",mp3Files[i].c_str());
|
Serial.printf("stream song: %s\n", buildFullPath(mp3Files[i]).c_str());
|
||||||
#endif
|
#endif
|
||||||
// Yield every few items to allow the async web server to send buffered data
|
// Yield every few items to allow the async web server to send buffered data
|
||||||
if (i % 5 == 4) {
|
if (i % 5 == 4) {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class DirectoryNode {
|
||||||
private:
|
private:
|
||||||
uint16_t id;
|
uint16_t id;
|
||||||
String name;
|
String name;
|
||||||
|
String dirPath;
|
||||||
std::vector<DirectoryNode*> subdirectories;
|
std::vector<DirectoryNode*> subdirectories;
|
||||||
std::vector<String> mp3Files;
|
std::vector<String> mp3Files;
|
||||||
std::vector<uint16_t> ids;
|
std::vector<uint16_t> ids;
|
||||||
|
|
@ -21,6 +22,7 @@ private:
|
||||||
uint16_t currentPlayingId = 0;
|
uint16_t currentPlayingId = 0;
|
||||||
uint16_t secondsPlayed = 0;
|
uint16_t secondsPlayed = 0;
|
||||||
|
|
||||||
|
String buildFullPath(const String& fileName) const;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -33,6 +35,8 @@ public:
|
||||||
const uint16_t getId() const;
|
const uint16_t getId() const;
|
||||||
const std::vector<DirectoryNode*>& getSubdirectories() const;
|
const std::vector<DirectoryNode*>& getSubdirectories() const;
|
||||||
const std::vector<String>& getMP3Files() const;
|
const std::vector<String>& getMP3Files() const;
|
||||||
|
const String& getDirPath() const;
|
||||||
|
String getFullPathByIndex(size_t index) const;
|
||||||
|
|
||||||
size_t getNumOfFiles() const;
|
size_t getNumOfFiles() const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ static const char* hdr_connection_key = "Connection";
|
||||||
|
|
||||||
static const char* hdr_connection_val = "close";
|
static const char* hdr_connection_val = "close";
|
||||||
|
|
||||||
|
const size_t buffer_size = 256;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
166
src/main.cpp
166
src/main.cpp
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
#include <ESPAsyncWebServer.h> //Local WebServer used to serve the configuration portal
|
#include <ESPAsyncWebServer.h> //Local WebServer used to serve the configuration portal
|
||||||
#include <ESPAsyncWiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
|
#include <ESPAsyncWiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
|
||||||
#include <ArduinoJson.h>
|
|
||||||
|
|
||||||
#include "Audio.h"
|
#include "Audio.h"
|
||||||
|
|
||||||
|
|
@ -682,36 +681,6 @@ boolean readSongProgress(const char *filename)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getState()
|
|
||||||
{
|
|
||||||
// Use static buffer to avoid repeated allocations
|
|
||||||
static DynamicJsonDocument jsonState(512);
|
|
||||||
static String output;
|
|
||||||
|
|
||||||
output.clear();
|
|
||||||
output.reserve(512); // Pre-allocate string buffer
|
|
||||||
|
|
||||||
jsonState["playing"] = audio.isRunning();
|
|
||||||
|
|
||||||
if (currentNode != nullptr && !currentNode->getCurrentPlaying().isEmpty())
|
|
||||||
jsonState["title"] = currentNode->getCurrentPlaying();
|
|
||||||
else
|
|
||||||
jsonState["title"] = "Stopped";
|
|
||||||
jsonState["filepath"] = currentNode->getCurrentPlaying();
|
|
||||||
|
|
||||||
|
|
||||||
jsonState["time"] = audio.getAudioCurrentTime();
|
|
||||||
jsonState["volume"] = audio.getVolume();
|
|
||||||
jsonState["length"] = audio.getAudioFileDuration();
|
|
||||||
jsonState["voltage"] = lastVoltage;
|
|
||||||
jsonState["uid"] = lastUid;
|
|
||||||
jsonState["heap"] = free_heap;
|
|
||||||
|
|
||||||
serializeJson(jsonState, output);
|
|
||||||
jsonState.clear();
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to save the rfid_map to the mapping file
|
// Function to save the rfid_map to the mapping file
|
||||||
void saveMappingToFile(const String filename)
|
void saveMappingToFile(const String filename)
|
||||||
|
|
@ -863,6 +832,113 @@ String processor(const String &var)
|
||||||
return String(); // Return empty string instead of creating new String
|
return String(); // Return empty string instead of creating new String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memory-optimized helpers and streamers to avoid large temporary Strings
|
||||||
|
|
||||||
|
static inline void htmlEscapeAndPrint(Print &out, const String &s)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < s.length(); ++i)
|
||||||
|
{
|
||||||
|
char c = s[i];
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '&': out.print(F("&")); break;
|
||||||
|
case '<': out.print(F("<")); break;
|
||||||
|
case '>': out.print(F(">")); break;
|
||||||
|
case '\"': out.print(F("\"")); break;
|
||||||
|
case '\'': out.print(F("'")); break;
|
||||||
|
default: out.print(c); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void jsonEscapeAndPrint(Print &out, const String &s)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < s.length(); ++i)
|
||||||
|
{
|
||||||
|
char c = s[i];
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '\"': out.print(F("\\\"")); break;
|
||||||
|
case '\\': out.print(F("\\\\")); break;
|
||||||
|
case '\b': out.print(F("\\b")); break;
|
||||||
|
case '\f': out.print(F("\\f")); break;
|
||||||
|
case '\n': out.print(F("\\n")); break;
|
||||||
|
case '\r': out.print(F("\\r")); break;
|
||||||
|
case '\t': out.print(F("\\t")); break;
|
||||||
|
default:
|
||||||
|
if ((uint8_t)c < 0x20) { // control chars as \u00XX
|
||||||
|
out.print(F("\\u00"));
|
||||||
|
const char hex[] = "0123456789ABCDEF";
|
||||||
|
out.print(hex[(c >> 4) & 0x0F]);
|
||||||
|
out.print(hex[c & 0x0F]);
|
||||||
|
} else {
|
||||||
|
out.print(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void streamMappingHTML(Print &out)
|
||||||
|
{
|
||||||
|
out.print(F("<table style='width:100%;border-collapse:collapse;'><tr><th style='border:1px solid #ccc;padding:4px;'>RFID</th><th style='border:1px solid #ccc;padding:4px;'>Song</th></tr>"));
|
||||||
|
for (const auto &pair : rfid_map)
|
||||||
|
{
|
||||||
|
out.print(F("<tr><td style='border:1px solid #ccc;padding:4px;'>"));
|
||||||
|
htmlEscapeAndPrint(out, pair.first);
|
||||||
|
out.print(F("</td><td style='border:1px solid #ccc;padding:4px;'>"));
|
||||||
|
// target|mode
|
||||||
|
htmlEscapeAndPrint(out, pair.second.target);
|
||||||
|
out.print(F("|"));
|
||||||
|
out.print(pair.second.mode);
|
||||||
|
out.print(F("</td></tr>"));
|
||||||
|
// Yield occasionally if async server is buffering
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
out.print(F("</table>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void streamStateJSON(Print &out)
|
||||||
|
{
|
||||||
|
const bool isRunning = audio.isRunning();
|
||||||
|
static const String emptyStr;
|
||||||
|
const String ¤t =
|
||||||
|
(currentNode != nullptr) ? currentNode->getCurrentPlaying() : emptyStr;
|
||||||
|
|
||||||
|
out.print(F("{\"playing\":"));
|
||||||
|
out.print(isRunning ? F("true") : F("false"));
|
||||||
|
|
||||||
|
out.print(F(",\"title\":\""));
|
||||||
|
if (!current.isEmpty()) jsonEscapeAndPrint(out, current);
|
||||||
|
else out.print(F("Stopped"));
|
||||||
|
out.print(F("\""));
|
||||||
|
|
||||||
|
out.print(F(",\"filepath\":\""));
|
||||||
|
jsonEscapeAndPrint(out, current);
|
||||||
|
out.print(F("\""));
|
||||||
|
|
||||||
|
out.print(F(",\"time\":"));
|
||||||
|
out.print(audio.getAudioCurrentTime());
|
||||||
|
|
||||||
|
out.print(F(",\"volume\":"));
|
||||||
|
out.print(audio.getVolume());
|
||||||
|
|
||||||
|
out.print(F(",\"length\":"));
|
||||||
|
out.print(audio.getAudioFileDuration());
|
||||||
|
|
||||||
|
out.print(F(",\"voltage\":"));
|
||||||
|
out.print(lastVoltage);
|
||||||
|
|
||||||
|
out.print(F(",\"uid\":\""));
|
||||||
|
jsonEscapeAndPrint(out, lastUid);
|
||||||
|
out.print(F("\""));
|
||||||
|
|
||||||
|
out.print(F(",\"heap\":"));
|
||||||
|
out.print(free_heap);
|
||||||
|
|
||||||
|
out.print(F("}"));
|
||||||
|
}
|
||||||
|
|
||||||
void stop()
|
void stop()
|
||||||
{
|
{
|
||||||
if (audio.isRunning())
|
if (audio.isRunning())
|
||||||
|
|
@ -1132,7 +1208,7 @@ static void serveStaticFile(AsyncWebServerRequest *request,
|
||||||
auto resp = request->beginChunkedResponse(contentType,
|
auto resp = request->beginChunkedResponse(contentType,
|
||||||
[ctx](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
|
[ctx](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
|
||||||
size_t toRead = maxLen;
|
size_t toRead = maxLen;
|
||||||
if (toRead > 512) toRead = 512;
|
if (toRead > buffer_size) toRead = buffer_size;
|
||||||
sd_lock_acquire();
|
sd_lock_acquire();
|
||||||
size_t n = ctx->f.read(buffer, toRead);
|
size_t n = ctx->f.read(buffer, toRead);
|
||||||
sd_lock_release();
|
sd_lock_release();
|
||||||
|
|
@ -1217,32 +1293,32 @@ void init_webserver() {
|
||||||
{
|
{
|
||||||
webreq_enter();
|
webreq_enter();
|
||||||
request->onDisconnect([](){ webreq_exit();});
|
request->onDisconnect([](){ webreq_exit();});
|
||||||
// Stream mapping to avoid Content-Length mismatches and reduce heap spikes
|
// Stream mapping to avoid building a large HTML String
|
||||||
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, 512);
|
AsyncResponseStream* stream = request->beginResponseStream(txt_html_charset, 512);
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
Serial.printf("Serving /mapping heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
|
Serial.printf("Serving /mapping heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
|
||||||
#endif
|
#endif
|
||||||
stream->addHeader(hdr_cache_control_key, hdr_cache_control_val);
|
stream->addHeader(hdr_cache_control_key, hdr_cache_control_val);
|
||||||
stream->addHeader(hdr_connection_key, hdr_connection_val);
|
stream->addHeader(hdr_connection_key, hdr_connection_val);
|
||||||
String html = processor(String("MAPPING"));
|
streamMappingHTML(*stream);
|
||||||
stream->print(html);
|
|
||||||
request->send(stream);
|
request->send(stream);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request)
|
server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||||
{
|
{
|
||||||
webreq_enter();
|
webreq_enter();
|
||||||
request->onDisconnect([](){ webreq_exit(); });
|
request->onDisconnect([](){ webreq_exit(); });
|
||||||
String state = getState();
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
Serial.printf("Serving /state heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
|
Serial.printf("Serving /state heap=%u webreq_cnt=%u\n", (unsigned)xPortGetFreeHeapSize(), (unsigned)webreq_cnt);
|
||||||
#endif
|
#endif
|
||||||
|
// Stream JSON directly to avoid DynamicJsonDocument/String allocations
|
||||||
AsyncWebServerResponse* resp = request->beginResponse(200, F("application/json; charset=UTF-8"), state);
|
AsyncResponseStream* stream = request->beginResponseStream(F("application/json; charset=UTF-8"), 256);
|
||||||
resp->addHeader(hdr_cache_control_key, hdr_cache_control_val);
|
stream->addHeader(hdr_cache_control_key, hdr_cache_control_val);
|
||||||
resp->addHeader(hdr_connection_key, hdr_connection_val);
|
stream->addHeader(hdr_connection_key, hdr_connection_val);
|
||||||
request->send(resp); });
|
streamStateJSON(*stream);
|
||||||
|
request->send(stream);
|
||||||
|
});
|
||||||
|
|
||||||
server.on("/start", HTTP_GET, [](AsyncWebServerRequest *request)
|
server.on("/start", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||||
{
|
{
|
||||||
|
|
@ -1509,6 +1585,8 @@ void loop()
|
||||||
if (webreq_cnt > 0 && webrequest_blockings > MAX_WEBREQUEST_BLOCKINGS) {
|
if (webreq_cnt > 0 && webrequest_blockings > MAX_WEBREQUEST_BLOCKINGS) {
|
||||||
Serial.println(F("excessive webrequest blocking - suppress reset"));
|
Serial.println(F("excessive webrequest blocking - suppress reset"));
|
||||||
// Avoid resetting server mid-response to prevent mixing headers/body or truncation
|
// Avoid resetting server mid-response to prevent mixing headers/body or truncation
|
||||||
|
server.reset();
|
||||||
|
init_webserver();
|
||||||
webreq_cnt = 0;
|
webreq_cnt = 0;
|
||||||
webrequest_blockings = 0;
|
webrequest_blockings = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,6 @@
|
||||||
|
|
||||||
#define MAX_VOL 15
|
#define MAX_VOL 15
|
||||||
|
|
||||||
//#define DEBUG TRUE
|
|
||||||
|
|
||||||
File root;
|
File root;
|
||||||
File mp3File;
|
File mp3File;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue