[ai] Fixed CSS not loaded error, volume via buttons,...
This commit is contained in:
parent
02cd5da886
commit
b8e1263fb3
143
src/main.cpp
143
src/main.cpp
|
|
@ -77,6 +77,24 @@ DirectoryNode rootNode("/");
|
|||
DirectoryNode *currentNode = nullptr;
|
||||
|
||||
volatile bool newRfidInt = false;
|
||||
volatile bool playButtonDown = false;
|
||||
volatile uint8_t sd_lock_flag = 0;
|
||||
|
||||
/* Simple spinlock using older GCC sync builtins (no libatomic required).
|
||||
sd_lock_acquire() will block (with a small delay) until the lock is free.
|
||||
sd_lock_release() releases the lock. This is sufficient for short SD ops. */
|
||||
static inline void sd_lock_acquire()
|
||||
{
|
||||
while (__sync_lock_test_and_set(&sd_lock_flag, 1))
|
||||
{
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void sd_lock_release()
|
||||
{
|
||||
__sync_lock_release(&sd_lock_flag);
|
||||
}
|
||||
|
||||
MFRC522 rfid(CS_RFID, RST_RFID); // instatiate a MFRC522 reader object.
|
||||
|
||||
|
|
@ -219,13 +237,16 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
|
|||
Serial.println(filepath);
|
||||
}
|
||||
|
||||
// Open the file for writing
|
||||
// Open the file for writing (guard with simple mutex)
|
||||
sd_lock_acquire();
|
||||
request->_tempFile = SD.open(filepath, FILE_WRITE);
|
||||
if (!request->_tempFile)
|
||||
{
|
||||
sd_lock_release();
|
||||
request->send(500, "text/plain", "Failed to create file on SD card");
|
||||
return;
|
||||
}
|
||||
sd_lock_release();
|
||||
}
|
||||
|
||||
if (len)
|
||||
|
|
@ -237,11 +258,14 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
|
|||
return;
|
||||
}
|
||||
|
||||
// Write data and verify bytes written
|
||||
// Write data and verify bytes written (guard writes with simple mutex)
|
||||
sd_lock_acquire();
|
||||
size_t bytesWritten = request->_tempFile.write(data, len);
|
||||
if (bytesWritten != len)
|
||||
{
|
||||
// ensure we close while holding the lock to keep SD state consistent
|
||||
request->_tempFile.close();
|
||||
sd_lock_release();
|
||||
request->send(500, "text/plain", "Write error - SD card may be full");
|
||||
return;
|
||||
}
|
||||
|
|
@ -251,6 +275,7 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
|
|||
{ // Flush every 2KB
|
||||
request->_tempFile.flush();
|
||||
}
|
||||
sd_lock_release();
|
||||
|
||||
// Reduce logging frequency to save memory - log every 200KB instead of 100KB
|
||||
if (len && (index % 204800 == 0))
|
||||
|
|
@ -266,8 +291,10 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
|
|||
{
|
||||
if (request->_tempFile)
|
||||
{
|
||||
sd_lock_acquire();
|
||||
request->_tempFile.flush(); // Ensure all data is written
|
||||
request->_tempFile.close();
|
||||
sd_lock_release();
|
||||
|
||||
logBuffer = "Upload Complete: ";
|
||||
logBuffer += filename;
|
||||
|
|
@ -376,17 +403,13 @@ void playSongById(uint16_t id, uint32_t continueSeconds = 0)
|
|||
{
|
||||
Serial.println("Failed to play file: " + mp3File);
|
||||
currentNode = nullptr;
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
return;
|
||||
}
|
||||
|
||||
if (continueSeconds != 0)
|
||||
{
|
||||
audio.setAudioPlayPosition(continueSeconds);
|
||||
audio.setAudioPlayPosition(continueSeconds);
|
||||
}
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
|
||||
void playSongByName(String song)
|
||||
|
|
@ -428,13 +451,8 @@ void playSongByName(String song)
|
|||
{
|
||||
Serial.println("Failed to play file: " + mp3File);
|
||||
currentNode = nullptr;
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
return;
|
||||
}
|
||||
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
|
||||
void playSongByPath(String path)
|
||||
|
|
@ -540,13 +558,10 @@ void playSongByRFID(String id)
|
|||
{
|
||||
Serial.println("Failed to play mapped file: " + mp3File);
|
||||
currentNode = nullptr;
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -564,8 +579,12 @@ bool playFile(const char *filename, uint32_t resumeFilePos)
|
|||
Serial.println("filename empty.");
|
||||
return false;
|
||||
}
|
||||
// return audio.connecttoFS(filename, resumeFilePos);
|
||||
return audio.connecttoFS(SD, filename, resumeFilePos);
|
||||
// Serialize access to SD when audio opens the file (short critical section)
|
||||
bool result = false;
|
||||
sd_lock_acquire();
|
||||
result = audio.connecttoFS(SD, filename, resumeFilePos);
|
||||
sd_lock_release();
|
||||
return result;
|
||||
}
|
||||
|
||||
void playNextMp3()
|
||||
|
|
@ -592,12 +611,16 @@ void playNextMp3()
|
|||
|
||||
Serial.print("Advancing to ");
|
||||
String mp3File = currentNode->getCurrentPlayingFilePath();
|
||||
//FIXME crash here if last song.
|
||||
if (mp3File.isEmpty()) {
|
||||
|
||||
currentNode = rootNode.findFirstDirectoryWithMP3s();
|
||||
return;
|
||||
}
|
||||
Serial.println(mp3File);
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
playFile(mp3File.c_str());
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
|
||||
void audio_info(const char *info)
|
||||
|
|
@ -991,8 +1014,6 @@ void previous()
|
|||
deactivateRFID();
|
||||
activateSD();
|
||||
playFile(currentNode->getCurrentPlayingFilePath().c_str());
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -1010,11 +1031,7 @@ void previous()
|
|||
Serial.println(*globalPrevSong);
|
||||
currentNode = globalPrevNode;
|
||||
stop();
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
playFile(currentNode->getCurrentPlayingFilePath().c_str());
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1070,8 +1087,6 @@ void audio_eof_mp3(const char *info)
|
|||
deactivateRFID();
|
||||
activateSD();
|
||||
playFile(currentNode->getCurrentPlayingFilePath().c_str());
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1088,8 +1103,6 @@ void audio_eof_mp3(const char *info)
|
|||
deactivateRFID();
|
||||
activateSD();
|
||||
playFile(currentNode->getCurrentPlayingFilePath().c_str());
|
||||
activateRFID();
|
||||
deactivateSD();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1238,6 +1251,8 @@ void setup()
|
|||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
webrequestActive = true;
|
||||
deactivateRFID();
|
||||
activateSD();
|
||||
String htmlPath = getSysDir("index.html");
|
||||
if (SD.exists(htmlPath))
|
||||
{
|
||||
|
|
@ -1256,6 +1271,9 @@ void setup()
|
|||
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))
|
||||
{
|
||||
|
|
@ -1264,7 +1282,7 @@ void setup()
|
|||
else
|
||||
{
|
||||
// Fallback: serve minimal CSS if file not found
|
||||
request->send(404, "text/plain", "ERROR: /system/style.css ot found!");
|
||||
request->send(404, "text/plain", "ERROR: /system/style.css not found!");
|
||||
}
|
||||
webrequestActive = false;
|
||||
});
|
||||
|
|
@ -1485,37 +1503,69 @@ void loop()
|
|||
else if (asyncNext)
|
||||
{
|
||||
asyncNext = false;
|
||||
if (audio.isRunning())
|
||||
// If the play/start button is held, treat NEXT as volume up
|
||||
if (playButtonDown)
|
||||
{
|
||||
next();
|
||||
uint8_t vol = audio.getVolume();
|
||||
if (vol < config.maxVolume)
|
||||
{
|
||||
vol++;
|
||||
audio.setVolume(vol);
|
||||
volume = vol; // update stored volume for mute/unmute
|
||||
}
|
||||
// do not play the startup sound when changing volume while holding play
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t vol = audio.getVolume();
|
||||
if (vol != config.maxVolume)
|
||||
if (audio.isRunning())
|
||||
{
|
||||
vol++;
|
||||
next();
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t vol = audio.getVolume();
|
||||
if (vol != config.maxVolume)
|
||||
{
|
||||
vol++;
|
||||
}
|
||||
audio.setVolume(vol);
|
||||
volume = vol;
|
||||
playSongByPath(getSysDir(startup_sound));
|
||||
}
|
||||
audio.setVolume(vol);
|
||||
playSongByPath(getSysDir(startup_sound));
|
||||
}
|
||||
}
|
||||
else if (asyncPrev)
|
||||
{
|
||||
asyncPrev = false;
|
||||
if (audio.isRunning())
|
||||
// If the play/start button is held, treat PREV as volume down
|
||||
if (playButtonDown)
|
||||
{
|
||||
previous();
|
||||
uint8_t vol = audio.getVolume();
|
||||
if (vol > 0)
|
||||
{
|
||||
vol--;
|
||||
audio.setVolume(vol);
|
||||
volume = vol; // update stored volume for mute/unmute
|
||||
}
|
||||
// do not play the startup sound when changing volume while holding play
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t vol = audio.getVolume();
|
||||
if (vol != 0)
|
||||
if (audio.isRunning())
|
||||
{
|
||||
vol--;
|
||||
previous();
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t vol = audio.getVolume();
|
||||
if (vol != 0)
|
||||
{
|
||||
vol--;
|
||||
}
|
||||
audio.setVolume(vol);
|
||||
volume = vol;
|
||||
playSongByPath(getSysDir(startup_sound));
|
||||
}
|
||||
audio.setVolume(vol);
|
||||
playSongByPath(getSysDir(startup_sound));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1563,6 +1613,9 @@ void loop2(void *parameter)
|
|||
|
||||
for (;;)
|
||||
{
|
||||
// Track whether the play/start button is currently held down so the main loop
|
||||
// can interpret NEXT/PREV as volume changes while play is held.
|
||||
playButtonDown = (digitalRead(BTN_START_STOP) == LOW);
|
||||
|
||||
if (buttonPressed(BTN_NEXT))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -354,3 +354,93 @@ function deleteFileOnServer() {
|
|||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
/* Ensure the site stylesheet loads reliably — retry loader if necessary
|
||||
Improved detection: verify a computed style from CSS is applied (safer than just checking stylesheet href).
|
||||
Retries with exponential backoff and deduplicates link tags we add. */
|
||||
(function ensureCssLoaded(){
|
||||
var retries = 0;
|
||||
var maxRetries = 6;
|
||||
|
||||
// Check a computed style that the stylesheet defines.
|
||||
// .status color in CSS is --muted: #6b7280 -> rgb(107, 114, 128)
|
||||
function isStyleApplied() {
|
||||
var el = document.querySelector('.status') || document.querySelector('.topbar');
|
||||
if (!el) return false;
|
||||
try {
|
||||
var color = getComputedStyle(el).color;
|
||||
// Expect "rgb(107, 114, 128)" when CSS is applied
|
||||
if (!color) return false;
|
||||
// Loose check for the three numeric components to be present
|
||||
return color.indexOf('107') !== -1 && color.indexOf('114') !== -1 && color.indexOf('128') !== -1;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function removeOldRetryLinks() {
|
||||
var links = Array.prototype.slice.call(document.querySelectorAll('link[data-retry-css]'));
|
||||
links.forEach(function(l){ l.parentNode.removeChild(l); });
|
||||
}
|
||||
|
||||
function tryLoad() {
|
||||
if (isStyleApplied()) {
|
||||
console.log('style.css appears applied');
|
||||
return;
|
||||
}
|
||||
if (retries >= maxRetries) {
|
||||
console.warn('style.css failed to apply after ' + retries + ' attempts');
|
||||
return;
|
||||
}
|
||||
retries++;
|
||||
// Remove previous retry-inserted links to avoid piling them up
|
||||
removeOldRetryLinks();
|
||||
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.setAttribute('data-retry-css', '1');
|
||||
// cache-busting query to force a fresh fetch when retrying
|
||||
link.href = 'style.css?cb=' + Date.now();
|
||||
var timeout = 800 + retries * 300; // increasing timeout per attempt
|
||||
|
||||
var done = false;
|
||||
function success() {
|
||||
if (done) return;
|
||||
done = true;
|
||||
// Give browser a short moment to apply rules
|
||||
setTimeout(function(){
|
||||
if (isStyleApplied()) {
|
||||
console.log('style.css loaded and applied (attempt ' + retries + ')');
|
||||
} else {
|
||||
console.warn('style.css loaded but styles not applied — retrying...');
|
||||
setTimeout(tryLoad, timeout);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
link.onload = success;
|
||||
link.onerror = function() {
|
||||
if (done) return;
|
||||
done = true;
|
||||
console.warn('style.css load error (attempt ' + retries + '), retrying...');
|
||||
setTimeout(tryLoad, timeout);
|
||||
};
|
||||
|
||||
// Append link to head
|
||||
document.head.appendChild(link);
|
||||
|
||||
// Safety check: if onload/onerror doesn't fire, verify computed style after timeout
|
||||
setTimeout(function(){
|
||||
if (done) return;
|
||||
if (isStyleApplied()) {
|
||||
console.log('style.css appears applied (delayed check)');
|
||||
} else {
|
||||
console.warn('style.css still not applied after timeout (attempt ' + retries + '), retrying...');
|
||||
setTimeout(tryLoad, timeout);
|
||||
}
|
||||
}, timeout + 300);
|
||||
}
|
||||
|
||||
// Start after a short delay to let the browser initiate initial requests
|
||||
setTimeout(tryLoad, 150);
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in New Issue