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