[ai] fixed web server issues, still some quirky behaviour. More robust.
This commit is contained in:
parent
b820f3fc8d
commit
1144b95349
269
src/main.cpp
269
src/main.cpp
|
|
@ -114,7 +114,11 @@ bool SDActive = false;
|
|||
|
||||
bool RFIDActive = false;
|
||||
|
||||
bool webrequestActive = false;
|
||||
|
||||
// Web request concurrency counter and helpers (atomic via GCC builtins)
|
||||
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); }
|
||||
|
||||
uint16_t voltage_threshold_counter = 0;
|
||||
|
||||
|
|
@ -319,6 +323,8 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
|
|||
|
||||
void handleMoveFile(AsyncWebServerRequest *request)
|
||||
{
|
||||
webreq_enter();
|
||||
request->onDisconnect([](){ webreq_exit(); });
|
||||
String from = request->arg("from");
|
||||
String to = request->arg("to");
|
||||
|
||||
|
|
@ -339,6 +345,8 @@ void handleMoveFile(AsyncWebServerRequest *request)
|
|||
|
||||
void handleDeleteFile(AsyncWebServerRequest *request)
|
||||
{
|
||||
webreq_enter();
|
||||
request->onDisconnect([](){ webreq_exit(); });
|
||||
String filename = request->arg("filename");
|
||||
|
||||
if (SD.exists(filename))
|
||||
|
|
@ -1147,6 +1155,136 @@ void readRFID()
|
|||
lastInteraction = millis();
|
||||
}
|
||||
|
||||
void init_webserver() {
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
webreq_enter();
|
||||
request->onDisconnect([](){ webreq_exit(); });
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
String htmlPath = getSysDir("index.html");
|
||||
if (SD.exists(htmlPath))
|
||||
{
|
||||
AsyncWebServerResponse *response = request->beginResponse(SD, htmlPath, "text/html");
|
||||
request->send(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: serve minimal error if file not found
|
||||
request->send(404, "text/plain", "ERROR: /system/index.html not found!");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
webreq_enter();
|
||||
request->onDisconnect([](){ webreq_exit(); });
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
// Ensure SD is active and RFID is deactivated while serving files.
|
||||
String cssPath = getSysDir("style.css");
|
||||
if (SD.exists(cssPath))
|
||||
{
|
||||
request->send(SD, cssPath, "text/css");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: serve minimal CSS if file not found
|
||||
request->send(404, "text/plain", "ERROR: /system/style.css not found!");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
server.on("/script.js", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
webreq_enter();
|
||||
request->onDisconnect([](){ webreq_exit(); });
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
String jsPath = getSysDir("script.js");
|
||||
if (SD.exists(jsPath))
|
||||
{
|
||||
request->send(SD, jsPath, "application/javascript");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: serve minimal JS if file not found
|
||||
request->send(404, "text/plain", "ERROR: /system/script.js not found!");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Dynamic endpoints to avoid template processing heap spikes
|
||||
server.on("/directory", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
String html = processor(String("DIRECTORY"));
|
||||
request->send(200, "text/html; charset=UTF-8", html);
|
||||
});
|
||||
|
||||
server.on("/mapping", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
String html = processor(String("MAPPING"));
|
||||
request->send(200, "text/html; charset=UTF-8", html);
|
||||
});
|
||||
|
||||
server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
String state = getState();
|
||||
request->send(200, "application/json charset=UTF-8", state.c_str()); });
|
||||
|
||||
server.on("/start", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain charset=UTF-8", "start");
|
||||
start(); });
|
||||
|
||||
server.on("/toggleplaypause", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain charset=UTF-8", "toggleplaypause");
|
||||
togglePlayPause(); });
|
||||
|
||||
server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain", "stop");
|
||||
stop(); });
|
||||
|
||||
server.on("/next", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain", "next");
|
||||
next(); });
|
||||
|
||||
server.on("/previous", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain", "previous");
|
||||
previous(); });
|
||||
|
||||
server.on("/playbyid", HTTP_GET, id_song_action);
|
||||
|
||||
server.on("/progress", HTTP_POST, progress_action);
|
||||
|
||||
server.on("/volume", HTTP_POST, volume_action);
|
||||
|
||||
server.on("/edit_mapping", HTTP_POST, editMapping);
|
||||
|
||||
// run handleUpload function when any file is uploaded
|
||||
server.on("/upload", HTTP_POST,
|
||||
[](AsyncWebServerRequest *request)
|
||||
{
|
||||
webreq_enter();
|
||||
request->onDisconnect([](){ webreq_exit(); });
|
||||
request->send(200);
|
||||
},
|
||||
handleUpload);
|
||||
|
||||
server.on("/move_file", HTTP_GET, handleMoveFile);
|
||||
server.on("/delete_file", HTTP_GET, handleDeleteFile);
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
|
@ -1245,111 +1383,7 @@ void setup()
|
|||
|
||||
if (wifiManager.autoConnect("HannaBox"))
|
||||
{
|
||||
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
webrequestActive = true;
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
String htmlPath = getSysDir("index.html");
|
||||
if (SD.exists(htmlPath))
|
||||
{
|
||||
AsyncWebServerResponse *response = request->beginResponse(SD, htmlPath, "text/html", false, processor);
|
||||
response->addHeader("Content-Type", "text/html; charset=UTF-8");
|
||||
request->send(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: serve minimal error if file not found
|
||||
request->send(404, "text/plain", "ERROR: /system/index.html not found!");
|
||||
}
|
||||
webrequestActive = false; });
|
||||
|
||||
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
webrequestActive = true;
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
// Ensure SD is active and RFID is deactivated while serving files.
|
||||
String cssPath = getSysDir("style.css");
|
||||
if (SD.exists(cssPath))
|
||||
{
|
||||
request->send(SD, cssPath, "text/css");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: serve minimal CSS if file not found
|
||||
request->send(404, "text/plain", "ERROR: /system/style.css not found!");
|
||||
}
|
||||
webrequestActive = false; });
|
||||
|
||||
server.on("/script.js", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
webrequestActive = true;
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
String jsPath = getSysDir("script.js");
|
||||
if (SD.exists(jsPath))
|
||||
{
|
||||
request->send(SD, jsPath, "application/javascript");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: serve minimal JS if file not found
|
||||
request->send(404, "text/plain", "ERROR: /system/script.js not found!");
|
||||
}
|
||||
webrequestActive = false; });
|
||||
|
||||
server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
String state = getState();
|
||||
request->send(200, "application/json charset=UTF-8", state.c_str()); });
|
||||
|
||||
server.on("/start", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain charset=UTF-8", "start");
|
||||
start(); });
|
||||
|
||||
server.on("/toggleplaypause", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain charset=UTF-8", "toggleplaypause");
|
||||
togglePlayPause(); });
|
||||
|
||||
server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain", "stop");
|
||||
stop(); });
|
||||
|
||||
server.on("/next", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain", "next");
|
||||
next(); });
|
||||
|
||||
server.on("/previous", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
|
||||
request->send(200, "text/plain", "previous");
|
||||
previous(); });
|
||||
|
||||
server.on("/playbyid", HTTP_GET, id_song_action);
|
||||
|
||||
server.on("/progress", HTTP_POST, progress_action);
|
||||
|
||||
server.on("/volume", HTTP_POST, volume_action);
|
||||
|
||||
server.on("/edit_mapping", HTTP_POST, editMapping);
|
||||
|
||||
// run handleUpload function when any file is uploaded
|
||||
server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request)
|
||||
{ request->send(200); }, handleUpload);
|
||||
|
||||
server.on("/move_file", HTTP_GET, handleMoveFile);
|
||||
server.on("/delete_file", HTTP_GET, handleDeleteFile);
|
||||
|
||||
init_webserver();
|
||||
server.begin();
|
||||
Serial.println("Wifi init");
|
||||
}
|
||||
|
|
@ -1434,15 +1468,16 @@ const String getSysDir(const String filename)
|
|||
|
||||
void loop()
|
||||
{
|
||||
if (webrequestActive && webrequest_blockings > 5000) {
|
||||
if (webreq_cnt > 0 && webrequest_blockings > 5000) {
|
||||
Serial.println("excessive webrequest blocking!");
|
||||
webreq_cnt = 0;
|
||||
webrequest_blockings = 0;
|
||||
webrequestActive = false;
|
||||
server.end();
|
||||
server.reset();
|
||||
init_webserver();
|
||||
server.begin();
|
||||
}
|
||||
|
||||
if (audio.isRunning() && !webrequestActive)
|
||||
if (audio.isRunning() && webreq_cnt == 0)
|
||||
{
|
||||
if (asyncStop)
|
||||
{
|
||||
|
|
@ -1465,13 +1500,13 @@ void loop()
|
|||
pendingSeek = false;
|
||||
}
|
||||
}
|
||||
else if (asyncStart && !webrequestActive)
|
||||
else if (asyncStart && webreq_cnt == 0)
|
||||
{
|
||||
asyncStart = false;
|
||||
start();
|
||||
}
|
||||
|
||||
if (continuePlaying && !webrequestActive)
|
||||
if (continuePlaying && webreq_cnt == 0)
|
||||
{
|
||||
continuePlaying = false;
|
||||
startupSoundPlayed = true;
|
||||
|
|
@ -1516,7 +1551,7 @@ void loop()
|
|||
asyncTogglePlayPause = false;
|
||||
togglePlayPause();
|
||||
}
|
||||
else if (asyncNext && !webrequestActive)
|
||||
else if (asyncNext && webreq_cnt == 0)
|
||||
{
|
||||
asyncNext = false;
|
||||
// If the play/start button is held, treat NEXT as volume up
|
||||
|
|
@ -1585,7 +1620,7 @@ void loop()
|
|||
}
|
||||
}
|
||||
|
||||
if (loopCounter % config.rfidLoopInterval == 0 && !webrequestActive)
|
||||
if (loopCounter % config.rfidLoopInterval == 0 && webreq_cnt == 0)
|
||||
{
|
||||
deactivateSD();
|
||||
activateRFID();
|
||||
|
|
@ -1597,7 +1632,7 @@ void loop()
|
|||
activateSD();
|
||||
}
|
||||
|
||||
if (loopCounter % VOLTAGE_LOOP_INTERVAL == 0 && !webrequestActive)
|
||||
if (loopCounter % VOLTAGE_LOOP_INTERVAL == 0 && webreq_cnt == 0)
|
||||
{
|
||||
lastVoltage = getBatteryVoltageMv();
|
||||
free_heap = xPortGetFreeHeapSize();
|
||||
|
|
@ -1620,8 +1655,10 @@ void loop()
|
|||
}
|
||||
}
|
||||
|
||||
if (webrequestActive) {
|
||||
if (webreq_cnt>0) {
|
||||
webrequest_blockings++;
|
||||
} else {
|
||||
webrequest_blockings = 0;
|
||||
}
|
||||
|
||||
loopCounter++;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ void progress_action(AsyncWebServerRequest *request);
|
|||
|
||||
void volume_action(AsyncWebServerRequest *request);
|
||||
|
||||
void init_webserver();
|
||||
|
||||
boolean buttonPressed(const uint8_t pin);
|
||||
|
||||
const String getSysDir(const String filename);
|
||||
|
|
|
|||
|
|
@ -75,9 +75,7 @@
|
|||
<h2>Playlist</h2>
|
||||
<button class="action-btn small" onclick="location.reload()">Refresh</button>
|
||||
</div>
|
||||
<div class="playlist-container">
|
||||
%DIRECTORY%
|
||||
</div>
|
||||
<div class="playlist-container" id="playlistContainer"></div>
|
||||
|
||||
<div class="manager-toggle">
|
||||
<button id="toggleFileManagerButton" class="action-btn" onclick="toggleFileManager()">Toggle Manager</button>
|
||||
|
|
@ -106,7 +104,7 @@
|
|||
|
||||
<h4>Edit RFID Mapping</h4>
|
||||
<p class="hint">Hint: Use a folder or filename, not the absolute file path!</p>
|
||||
<div class="mapping-list">%MAPPING%</div>
|
||||
<div class="mapping-list" id="mappingList"></div>
|
||||
|
||||
<form id="editMappingForm" class="form form-grid">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,59 @@
|
|||
setInterval(getState, 4000);
|
||||
setInterval(updateProgress, 500); // Update progress every second
|
||||
|
||||
// Get the <li> elements
|
||||
var liElements = document.querySelectorAll('ul li');
|
||||
/* Dynamic content loaders for playlist and mapping (avoid heavy template processing on server) */
|
||||
function bindPlaylistClicks() {
|
||||
var container = document.getElementById('playlistContainer');
|
||||
if (!container) return;
|
||||
container.onclick = function(e) {
|
||||
var li = e.target.closest('li');
|
||||
if (!li || !container.contains(li)) return;
|
||||
var id = li.dataset && li.dataset.id;
|
||||
if (id) playSongById(id);
|
||||
};
|
||||
}
|
||||
|
||||
function loadDirectory() {
|
||||
var container = document.getElementById('playlistContainer');
|
||||
if (!container) return;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/directory', true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
container.innerHTML = xhr.responseText || '';
|
||||
bindPlaylistClicks();
|
||||
} else {
|
||||
container.innerHTML = '<div class="hint">Failed to load playlist.</div>';
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function loadMapping() {
|
||||
var el = document.getElementById('mappingList');
|
||||
if (!el) return;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/mapping', true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
el.innerHTML = xhr.responseText || '';
|
||||
} else {
|
||||
el.innerHTML = '<div class="hint">Failed to load mapping.</div>';
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
/* Kick off dynamic loads on DOM ready */
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadDirectory();
|
||||
loadMapping();
|
||||
getState();
|
||||
});
|
||||
|
||||
var lastChange = 0;
|
||||
|
||||
|
|
@ -24,13 +75,7 @@ function formatTime(totalSec) {
|
|||
}
|
||||
|
||||
// Add click event listener to each <li> element
|
||||
liElements.forEach(function(li) {
|
||||
li.addEventListener('click', function() {
|
||||
//var liText = this.innerText;
|
||||
var id = this.dataset.id;
|
||||
playSongById(id);
|
||||
});
|
||||
});
|
||||
/* Clicks are handled via event delegation in bindPlaylistClicks() */
|
||||
|
||||
function simpleGetCall(endpoint) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
|
|
|||
Loading…
Reference in New Issue