[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 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;
|
uint16_t voltage_threshold_counter = 0;
|
||||||
|
|
||||||
|
|
@ -319,6 +323,8 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
|
||||||
|
|
||||||
void handleMoveFile(AsyncWebServerRequest *request)
|
void handleMoveFile(AsyncWebServerRequest *request)
|
||||||
{
|
{
|
||||||
|
webreq_enter();
|
||||||
|
request->onDisconnect([](){ webreq_exit(); });
|
||||||
String from = request->arg("from");
|
String from = request->arg("from");
|
||||||
String to = request->arg("to");
|
String to = request->arg("to");
|
||||||
|
|
||||||
|
|
@ -339,6 +345,8 @@ void handleMoveFile(AsyncWebServerRequest *request)
|
||||||
|
|
||||||
void handleDeleteFile(AsyncWebServerRequest *request)
|
void handleDeleteFile(AsyncWebServerRequest *request)
|
||||||
{
|
{
|
||||||
|
webreq_enter();
|
||||||
|
request->onDisconnect([](){ webreq_exit(); });
|
||||||
String filename = request->arg("filename");
|
String filename = request->arg("filename");
|
||||||
|
|
||||||
if (SD.exists(filename))
|
if (SD.exists(filename))
|
||||||
|
|
@ -1147,6 +1155,136 @@ void readRFID()
|
||||||
lastInteraction = millis();
|
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()
|
void setup()
|
||||||
{
|
{
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
|
@ -1245,111 +1383,7 @@ void setup()
|
||||||
|
|
||||||
if (wifiManager.autoConnect("HannaBox"))
|
if (wifiManager.autoConnect("HannaBox"))
|
||||||
{
|
{
|
||||||
|
init_webserver();
|
||||||
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);
|
|
||||||
|
|
||||||
server.begin();
|
server.begin();
|
||||||
Serial.println("Wifi init");
|
Serial.println("Wifi init");
|
||||||
}
|
}
|
||||||
|
|
@ -1434,15 +1468,16 @@ const String getSysDir(const String filename)
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
if (webrequestActive && webrequest_blockings > 5000) {
|
if (webreq_cnt > 0 && webrequest_blockings > 5000) {
|
||||||
Serial.println("excessive webrequest blocking!");
|
Serial.println("excessive webrequest blocking!");
|
||||||
|
webreq_cnt = 0;
|
||||||
webrequest_blockings = 0;
|
webrequest_blockings = 0;
|
||||||
webrequestActive = false;
|
server.reset();
|
||||||
server.end();
|
init_webserver();
|
||||||
server.begin();
|
server.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audio.isRunning() && !webrequestActive)
|
if (audio.isRunning() && webreq_cnt == 0)
|
||||||
{
|
{
|
||||||
if (asyncStop)
|
if (asyncStop)
|
||||||
{
|
{
|
||||||
|
|
@ -1465,13 +1500,13 @@ void loop()
|
||||||
pendingSeek = false;
|
pendingSeek = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (asyncStart && !webrequestActive)
|
else if (asyncStart && webreq_cnt == 0)
|
||||||
{
|
{
|
||||||
asyncStart = false;
|
asyncStart = false;
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (continuePlaying && !webrequestActive)
|
if (continuePlaying && webreq_cnt == 0)
|
||||||
{
|
{
|
||||||
continuePlaying = false;
|
continuePlaying = false;
|
||||||
startupSoundPlayed = true;
|
startupSoundPlayed = true;
|
||||||
|
|
@ -1516,7 +1551,7 @@ void loop()
|
||||||
asyncTogglePlayPause = false;
|
asyncTogglePlayPause = false;
|
||||||
togglePlayPause();
|
togglePlayPause();
|
||||||
}
|
}
|
||||||
else if (asyncNext && !webrequestActive)
|
else if (asyncNext && webreq_cnt == 0)
|
||||||
{
|
{
|
||||||
asyncNext = false;
|
asyncNext = false;
|
||||||
// If the play/start button is held, treat NEXT as volume up
|
// 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();
|
deactivateSD();
|
||||||
activateRFID();
|
activateRFID();
|
||||||
|
|
@ -1597,7 +1632,7 @@ void loop()
|
||||||
activateSD();
|
activateSD();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loopCounter % VOLTAGE_LOOP_INTERVAL == 0 && !webrequestActive)
|
if (loopCounter % VOLTAGE_LOOP_INTERVAL == 0 && webreq_cnt == 0)
|
||||||
{
|
{
|
||||||
lastVoltage = getBatteryVoltageMv();
|
lastVoltage = getBatteryVoltageMv();
|
||||||
free_heap = xPortGetFreeHeapSize();
|
free_heap = xPortGetFreeHeapSize();
|
||||||
|
|
@ -1620,8 +1655,10 @@ void loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webrequestActive) {
|
if (webreq_cnt>0) {
|
||||||
webrequest_blockings++;
|
webrequest_blockings++;
|
||||||
|
} else {
|
||||||
|
webrequest_blockings = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
loopCounter++;
|
loopCounter++;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ void progress_action(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
void volume_action(AsyncWebServerRequest *request);
|
void volume_action(AsyncWebServerRequest *request);
|
||||||
|
|
||||||
|
void init_webserver();
|
||||||
|
|
||||||
boolean buttonPressed(const uint8_t pin);
|
boolean buttonPressed(const uint8_t pin);
|
||||||
|
|
||||||
const String getSysDir(const String filename);
|
const String getSysDir(const String filename);
|
||||||
|
|
|
||||||
|
|
@ -75,9 +75,7 @@
|
||||||
<h2>Playlist</h2>
|
<h2>Playlist</h2>
|
||||||
<button class="action-btn small" onclick="location.reload()">Refresh</button>
|
<button class="action-btn small" onclick="location.reload()">Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="playlist-container">
|
<div class="playlist-container" id="playlistContainer"></div>
|
||||||
%DIRECTORY%
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="manager-toggle">
|
<div class="manager-toggle">
|
||||||
<button id="toggleFileManagerButton" class="action-btn" onclick="toggleFileManager()">Toggle Manager</button>
|
<button id="toggleFileManagerButton" class="action-btn" onclick="toggleFileManager()">Toggle Manager</button>
|
||||||
|
|
@ -106,7 +104,7 @@
|
||||||
|
|
||||||
<h4>Edit RFID Mapping</h4>
|
<h4>Edit RFID Mapping</h4>
|
||||||
<p class="hint">Hint: Use a folder or filename, not the absolute file path!</p>
|
<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">
|
<form id="editMappingForm" class="form form-grid">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,59 @@
|
||||||
setInterval(getState, 4000);
|
setInterval(getState, 4000);
|
||||||
setInterval(updateProgress, 500); // Update progress every second
|
setInterval(updateProgress, 500); // Update progress every second
|
||||||
|
|
||||||
// Get the <li> elements
|
/* Dynamic content loaders for playlist and mapping (avoid heavy template processing on server) */
|
||||||
var liElements = document.querySelectorAll('ul li');
|
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;
|
var lastChange = 0;
|
||||||
|
|
||||||
|
|
@ -24,13 +75,7 @@ function formatTime(totalSec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add click event listener to each <li> element
|
// Add click event listener to each <li> element
|
||||||
liElements.forEach(function(li) {
|
/* Clicks are handled via event delegation in bindPlaylistClicks() */
|
||||||
li.addEventListener('click', function() {
|
|
||||||
//var liText = this.innerText;
|
|
||||||
var id = this.dataset.id;
|
|
||||||
playSongById(id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function simpleGetCall(endpoint) {
|
function simpleGetCall(endpoint) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue