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 @@
-
+
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 = '';
-}