diff --git a/src/DirectoryNode.cpp b/src/DirectoryNode.cpp index 51386d3..a4f0322 100644 --- a/src/DirectoryNode.cpp +++ b/src/DirectoryNode.cpp @@ -1,4 +1,5 @@ #include "DirectoryNode.h" +#include "globals.h" DirectoryNode::DirectoryNode(const String &nodeName) : name(nodeName), currentPlaying(nullptr) @@ -465,7 +466,7 @@ String DirectoryNode::getCurrentPlayingFilePath() const { if (currentPlaying != nullptr) { - String filePath = "/" + name; + String filePath = name; if (!filePath.endsWith("/")) { filePath += "/"; diff --git a/src/DirectoryNode.h b/src/DirectoryNode.h index 03fd86a..e8be759 100644 --- a/src/DirectoryNode.h +++ b/src/DirectoryNode.h @@ -5,7 +5,7 @@ #include -const String sys_dir = "system"; + diff --git a/src/WebContent.h b/src/WebContent.h deleted file mode 100644 index ffd505c..0000000 --- a/src/WebContent.h +++ /dev/null @@ -1,68 +0,0 @@ -// HTML web page -const char index_html[] PROGMEM = R"rawliteral( - - - HannaBox - - - - - -

🎵 HannaBox 🎵

-

-
-
-
- -
- - -

-
-
- - - -
-
- - -
- - -

-

🎶 Playlist 🎶

- %DIRECTORY% -

- -
- - -
- -
- -

Edit RFID Mapping

-
- -
- -
- -
- - - -)rawliteral"; diff --git a/src/css.h b/src/css.h deleted file mode 100644 index 5e0ec7e..0000000 --- a/src/css.h +++ /dev/null @@ -1,3 +0,0 @@ -// CSS content moved to SD card at /system/style.css -// This saves approximately 4KB of flash memory -const char css[] PROGMEM = ""; diff --git a/src/globals.h b/src/globals.h index d7a165c..ac1fbd9 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1,93 +1,23 @@ #ifndef GLOBALS_H_ #define GLOBALS_H_ -void stop(); - -void start(); - -bool playFile(const char* filename, uint32_t resumeFilePos = 0); - -void loop2(void* parameter); - -void id_song_action(AsyncWebServerRequest *request); - -void progress_action(AsyncWebServerRequest *request); - -void volume_action(AsyncWebServerRequest *request); - -boolean buttonPressed(const uint8_t pin); - -/** - * Helper routine to dump a byte array as hex values to Serial. - */ -void dump_byte_array(byte *buffer, byte bufferSize) -{ - for (byte i = 0; i < bufferSize; i++) - { - Serial.print(buffer[i] < 0x10 ? " 0" : " "); - Serial.print(buffer[i], HEX); - } -} - -String getRFIDString(byte uidByte[10]) -{ - String uidString = String(uidByte[0]) + " " + String(uidByte[1]) + " " + - String(uidByte[2]) + " " + String(uidByte[3]); - return uidString; -} - -void writeFile(fs::FS &fs, const char * path, const char * message){ - Serial.printf("Writing file: %s\n", path); - - File file = fs.open(path, FILE_WRITE); - if(!file){ - Serial.println("Failed to open file for writing"); - return; - } - if(file.print(message)){ - Serial.println("File written"); - } else { - Serial.println("Write failed"); - } - file.close(); -} -unsigned long lastStart = 0; -unsigned long lastInteraction = 0; -boolean sleepSoundPlayed = false; -boolean startupSoundPlayed = false; -boolean continuousMode = false; -uint8_t buttontoignore = 0; - -uint32_t lastVoltage = 0; - -uint loopCounter = 0; - -String lastUid = ""; - -uint16_t currentSongId = 0; - -uint32_t currentSongSeconds = 0; - -boolean continuePlaying = false; - -boolean prepareSleepMode = false; +const String sys_dir = "system"; const String sleep_sound = "sleep.mp3"; const String startup_sound = "start.mp3"; -const String mapping_file = "/mapping.txt"; +const String mapping_file = "mapping.txt"; const String progress_file = "progress.txt"; -std::map rfid_map; /* const long sleepMessageDelay = 28000; diff --git a/src/main.cpp b/src/main.cpp index 5a53854..1385f68 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,14 +47,9 @@ #define MAX_VOL 15 #include "globals.h" -#include "WebContent.h" +#include "main.h" #include "DirectoryNode.h" -#define SOUND_STARTUP "start.mp3" -#define SOUND_SLEEP "sleep.mp3" - - - File root; File mp3File; @@ -280,7 +275,7 @@ void handleDeleteFile(AsyncWebServerRequest *request) { String filename = request->arg("filename"); if (SD.exists(filename)) { - SD.remove(filename.c_str()); + SD.remove(filename); Serial.println("Deleted file: " + filename); request->send(200, "text/plain", "File deleted successfully."); } else { @@ -492,7 +487,7 @@ String getState() } // Function to save the rfid_map to the mapping file -void saveMappingToFile(const char *filename) { +void saveMappingToFile(const String filename) { File file = SD.open(filename, FILE_WRITE); if (file) { for (const auto &pair : rfid_map) { @@ -511,7 +506,7 @@ void editMapping(AsyncWebServerRequest *request) { String rfid = request->getParam("rfid", true)->value(); String song = request->getParam("song", true)->value(); rfid_map[rfid] = song; - saveMappingToFile(mapping_file.c_str()); + saveMappingToFile(getSysDir(mapping_file)); request->send(200, "text/plain", "Mapping updated"); } else { request->send(400, "text/plain", "Invalid parameters"); @@ -752,7 +747,7 @@ void setup() - String progressPath = "/"+sys_dir+"/"+progress_file; + String progressPath = getSysDir(progress_file); continuePlaying = readSongProgress(progressPath.c_str()); @@ -824,14 +819,14 @@ void setup() server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { webrequestActive = true; - String htmlPath = "/system/index.html"; + 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(200, "text/plain", "ERROR: /system/index.html on SD Card not found!"); + request->send(404, "text/plain", "ERROR: /system/index.html on SD Card not found!"); } webrequestActive = false; @@ -840,12 +835,12 @@ void setup() server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request) { webrequestActive = true; - String cssPath = "/system/style.css"; + 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(200, "text/css", "body{font-family:Arial;text-align:center;padding:20px;}"); + request->send(404, "text/plain", "ERROR: /system/style.css on SD Card not found!"); } webrequestActive = false; @@ -856,12 +851,12 @@ void setup() webrequestActive = true; deactivateRFID(); activateSD(); - String jsPath = "/system/script.js"; + 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(200, "application/javascript", "console.log('JavaScript file not found on SD card');"); + request->send(404, "text/plain", "ERROR: /system/script.js on SD Card not found!"); } webrequestActive = false; @@ -990,9 +985,9 @@ void volume_action(AsyncWebServerRequest *request) request->send_P(200, "text/plain", "ok"); } -String getStartupSoundDir() { - static String tempPath = "/"+sys_dir+"/"+startup_sound; - return tempPath; + +const String getSysDir(const String filename) { + return "/"+sys_dir+"/"+filename; } void loop() @@ -1022,7 +1017,7 @@ void loop() playSongById(currentSongId,currentSongSeconds); } else if (!startupSoundPlayed) { startupSoundPlayed = true; - playSongByPath(getStartupSoundDir().c_str()); + playSongByPath(getSysDir(startup_sound)); } @@ -1034,12 +1029,12 @@ void loop() sleepSoundPlayed = true; prepareSleepMode = true; if (currentNode != NULL) { - String progressPath = "/"+sys_dir+"/"+progress_file; + String progressPath = getSysDir(progress_file); writeSongProgress(progressPath.c_str(),currentNode->getCurrentPlayingId(),currentNode->getSecondsPlayed()); } - String tempPath = "/"+sys_dir+"/"+sleep_sound; + String tempPath = getSysDir(sleep_sound); playSongByPath(tempPath.c_str()); } @@ -1067,7 +1062,7 @@ void loop() vol++; } audio.setVolume(vol); - playSongByPath(getStartupSoundDir().c_str()); + playSongByPath(getSysDir(startup_sound)); } @@ -1083,7 +1078,7 @@ void loop() vol--; } audio.setVolume(vol); - playSongByPath(getStartupSoundDir().c_str()); + playSongByPath(getSysDir(startup_sound)); } } diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..cc9a9f6 --- /dev/null +++ b/src/main.h @@ -0,0 +1,85 @@ +#ifndef MAIN_H_ +#define MAIN_H_ + +void stop(); + +void start(); + +bool playFile(const char* filename, uint32_t resumeFilePos = 0); + +void loop2(void* parameter); + +void id_song_action(AsyncWebServerRequest *request); + +void progress_action(AsyncWebServerRequest *request); + +void volume_action(AsyncWebServerRequest *request); + +boolean buttonPressed(const uint8_t pin); + +const String getSysDir(const String filename); + +/** + * Helper routine to dump a byte array as hex values to Serial. + */ +void dump_byte_array(byte *buffer, byte bufferSize) +{ + for (byte i = 0; i < bufferSize; i++) + { + Serial.print(buffer[i] < 0x10 ? " 0" : " "); + Serial.print(buffer[i], HEX); + } +} + +String getRFIDString(byte uidByte[10]) +{ + String uidString = String(uidByte[0]) + " " + String(uidByte[1]) + " " + + String(uidByte[2]) + " " + String(uidByte[3]); + return uidString; +} + +void writeFile(fs::FS &fs, const char * path, const char * message){ + Serial.printf("Writing file: %s\n", path); + + File file = fs.open(path, FILE_WRITE); + if(!file){ + Serial.println("Failed to open file for writing"); + return; + } + if(file.print(message)){ + Serial.println("File written"); + } else { + Serial.println("Write failed"); + } + file.close(); +} + +unsigned long lastStart = 0; + +unsigned long lastInteraction = 0; + +boolean sleepSoundPlayed = false; + +boolean startupSoundPlayed = false; + +boolean continuousMode = false; + +uint8_t buttontoignore = 0; + +uint32_t lastVoltage = 0; + +uint loopCounter = 0; + +String lastUid = ""; + +uint16_t currentSongId = 0; + +uint32_t currentSongSeconds = 0; + +boolean continuePlaying = false; + +boolean prepareSleepMode = false; + +std::map rfid_map; + +#endif \ No newline at end of file diff --git a/web/index.html b/web/index.html index 72e1079..60c97a6 100644 --- a/web/index.html +++ b/web/index.html @@ -75,13 +75,13 @@ - +

Delete File

- - - + + +
diff --git a/web/script.js b/web/script.js index 52e45ad..5d19eb5 100644 --- a/web/script.js +++ b/web/script.js @@ -86,9 +86,10 @@ function displayState(state) { document.getElementById("uid").innerHTML = 'Last NFC ID: '+state['uid']; /* ==== Autofill convenience fields ==== */ - if (state['filepath']) { + var fm = document.getElementById('fileManager'); + if (state['filepath'] && fm.style.display == 'none') { document.getElementById('moveFrom').value = state['filepath']; - document.getElementById('deleteFile').value = state['filepath']; + document.getElementById('deleteFileName').value = state['filepath']; document.getElementById('song').value = state['filepath']; } if (state['uid']) { @@ -304,8 +305,8 @@ function moveFile() { xhr.send(); } -function deleteFile() { - var filename = document.getElementById('deleteFile').value.trim(); +function deleteFileOnServer() { + var filename = document.getElementById('deleteFileName').value.trim(); if (!filename) { alert('Please provide filename to delete.'); return; diff --git a/web/system_script.js b/web/system_script.js deleted file mode 100644 index 51fd815..0000000 --- a/web/system_script.js +++ /dev/null @@ -1,262 +0,0 @@ -setInterval(getState, 4000); -setInterval(updateProgress, 500); // Update progress every second - -// Get the
  • elements -var liElements = document.querySelectorAll('ul li'); - -var lastChange = 0; - -var lastStateUpdateTime = Date.now(); -var songStartTime = 0; -var currentSongLength = 0; -var isPlaying = false; -var userIsInteracting = false; // Flag to track user interaction with the slider - -// Add click event listener to each
  • element -liElements.forEach(function(li) { - li.addEventListener('click', function() { - //var liText = this.innerText; - var id = this.dataset.id; - playSongById(id); - }); -}); - -function simpleGetCall(endpoint) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", "/" + endpoint, true); - - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - getState(); // Fetch the latest state right after the button action - } - }; - - xhr.send(); -} - -function postValue(endpoint,value) { - var xhr = new XMLHttpRequest(); - xhr.open("POST", "/" + endpoint, true); - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); - xhr.send("value="+encodeURIComponent(value)); - - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - getState(); // Fetch the latest state right after the button action - } - }; -} - -function getState() { - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - var state = JSON.parse(xhr.response); - isPlaying = state['playing']; - if (isPlaying) { - songStartTime = Date.now() - state['time'] * 1000; - currentSongLength = state['length'] * 1000; - } - lastStateUpdateTime = Date.now(); - displayState(state); - } - } - xhr.open("GET","/state", true); - xhr.send(); -} - -function updateProgress() { - if (isPlaying && !userIsInteracting) { // Check if user is not interacting - var elapsedTime = Date.now() - songStartTime; - if (elapsedTime >= currentSongLength) { - elapsedTime = currentSongLength; - isPlaying = false; // Stop updating if the song has ended - } - var progressElement = document.getElementById('progressSlider'); - progressElement.value = elapsedTime / 1000; // Convert to seconds - document.getElementById("progressLabel").innerHTML = Math.floor(elapsedTime / 1000); - } -} - -function displayState(state) { - document.getElementById("state").innerHTML = state['title']; - document.getElementById("progressLabel").innerHTML = state['time']; - document.getElementById("voltage").innerHTML = state['voltage']+' mV'; - document.getElementById("heap").innerHTML = state['heap']+' bytes free heap'; - document.getElementById("uid").innerHTML = 'Last NFC ID: '+state['uid']; - var elements = document.getElementsByClassName('play-button'); - var btn = elements[0]; - - if (state['playing']) { - btn.classList.add('paused'); - } else { - btn.classList.remove('paused'); - } - - if (Date.now()-lastChange>1200) { - var progress = document.getElementById('progressSlider'); - progress.value = state['time']; - progress.max = state['length']; - - var volume = document.getElementById('volumeSlider'); - volume.value = state['volume']; - } - updateProgress(); -} - -function playSongById(id) { - var url = "/playbyid"; - var params = "id="+id; - var http = new XMLHttpRequest(); - - http.open("GET", url+"?"+params, true); - http.onreadystatechange = function() - { - if(http.readyState == 4 && http.status == 200) { - getState(); - } - } - http.send(null); -} - -function playNamedSong(song) { - var xhr = new XMLHttpRequest(); - xhr.open("POST", "/playnamed"); - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); - //application/x-www-form-urlencoded - var body = song; - xhr.send("title="+encodeURIComponent(body)); -} - -function editMapping() { - var rfid = document.getElementById('rfid').value; - var song = document.getElementById('song').value; - var xhr = new XMLHttpRequest(); - xhr.open("POST", "/edit_mapping", true); - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); - xhr.send("rfid=" + encodeURIComponent(rfid) + "&song=" + encodeURIComponent(song)); - xhr.onreadystatechange = function() { - if (xhr.readyState === 4 && xhr.status === 200) { - alert("Mapping updated successfully!"); - } - }; - } - -// Validate file before upload -function validateFile(file) { - var maxSize = 50 * 1024 * 1024; // 50MB limit - var allowedTypes = ['audio/mpeg', 'audio/wav', 'audio/flac', 'audio/mp4', 'audio/ogg']; - var allowedExtensions = ['.mp3', '.wav', '.flac', '.m4a', '.ogg']; - - if (file.size > maxSize) { - return 'File too large. Maximum size is 50MB.'; - } - - var fileName = file.name.toLowerCase(); - var hasValidExtension = allowedExtensions.some(ext => fileName.endsWith(ext)); - - if (!hasValidExtension) { - return 'Invalid file type. Only audio files (.mp3, .wav, .flac, .m4a, .ogg) are allowed.'; - } - - return null; // No error -} - -// Handle form submission with AJAX to show upload status -document.getElementById('uploadForm').addEventListener('submit', function(event) { - event.preventDefault(); // Prevent the default form submit - - var fileInput = document.getElementById('uploadFile'); - var file = fileInput.files[0]; - - if (!file) { - alert('Please select a file to upload.'); - return; - } - - var validationError = validateFile(file); - if (validationError) { - alert(validationError); - return; - } - - var form = event.target; - var formData = new FormData(form); - var uploadButton = document.getElementById('uploadButton'); - var uploadStatus = document.getElementById('uploadStatus'); - var uploadProgress = document.getElementById('uploadProgress'); - var progressFill = document.getElementById('progressFill'); - var progressText = document.getElementById('progressText'); - - // Disable upload button and show progress - uploadButton.disabled = true; - uploadButton.value = 'Uploading...'; - uploadProgress.style.display = 'block'; - uploadStatus.innerHTML = 'Preparing upload...'; - - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/upload', true); - - xhr.upload.onloadstart = function() { - uploadStatus.innerHTML = 'Upload started...'; - }; - - xhr.upload.onprogress = function(event) { - if (event.lengthComputable) { - var percentComplete = Math.round((event.loaded / event.total) * 100); - progressFill.style.width = percentComplete + '%'; - progressText.innerHTML = percentComplete + '%'; - uploadStatus.innerHTML = 'Uploading: ' + percentComplete + '% (' + - Math.round(event.loaded / 1024) + 'KB / ' + - Math.round(event.total / 1024) + 'KB)'; - } - }; - - xhr.upload.onerror = function() { - uploadStatus.innerHTML = 'Upload failed due to network error.'; - resetUploadForm(); - }; - - xhr.upload.onabort = function() { - uploadStatus.innerHTML = 'Upload was cancelled.'; - resetUploadForm(); - }; - - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { // Request is done - if (xhr.status >= 200 && xhr.status < 300) { // Success status code range - uploadStatus.innerHTML = 'Upload completed successfully!'; - progressFill.style.width = '100%'; - progressText.innerHTML = '100%'; - - setTimeout(function() { - alert('File uploaded successfully!'); - location.reload(); // Reload to get updated playlist - }, 1000); - } else { - var errorMsg = xhr.responseText || 'Unknown error occurred'; - uploadStatus.innerHTML = 'Upload failed: ' + errorMsg; - alert('Upload failed: ' + errorMsg); - resetUploadForm(); - } - } - }; - - xhr.send(formData); // Send the form data using XMLHttpRequest -}); - -function resetUploadForm() { - var uploadButton = document.getElementById('uploadButton'); - var uploadProgress = document.getElementById('uploadProgress'); - var progressFill = document.getElementById('progressFill'); - var progressText = document.getElementById('progressText'); - - uploadButton.disabled = false; - uploadButton.value = 'Upload'; - uploadProgress.style.display = 'none'; - progressFill.style.width = '0%'; - progressText.innerHTML = '0%'; - - // Clear file input - document.getElementById('uploadFile').value = ''; -}