diff --git a/README.md b/README.md index 2660240..d8af890 100644 --- a/README.md +++ b/README.md @@ -191,5 +191,8 @@ Everything that can be controlled via RFID-tags and buttons, can also be control ### Supported file/stream-types Please refer [ESP32-audioI2S](https://github.com/schreibfaul1/ESP32-audioI2S), as this is the library I used for music-decoding. +### Backups +As all assignments between RFID-IDs and actions (playmode, file to play...) is saved in ESP's NVS, the problem is that it's all gone when the ESP is broken. So that's where a backup comes into play. So every time you change or add a new assignment between a RFID-tag and an action via GUI, a backup-file is saved on the uSD-card. The file's name can be changed via `backupFile`. Again using the GUI you can use the upload-form to import such a file. To be honest: Sometimes I had some issues with Firefox doing this whereas Safari turned out to do it right. Don't know why :-(. + ## Smarthome (optional) As already described, MQTT is supported. In order to use it it's necessary to run a MQTT-broker; [Mosquitto](https://mosquitto.org/) for instance. After connecting to it, Tonuino subscribes to all command-topics. State-topics are used to push states to the broker in order to inform others if anything changed (change of volume, new playlist, new track... name it). Others, like openHAB, subscribe to state-topics end send commands via command-topics. So it's not just limited to openHAB. It's just necessary to use a platform, that supports MQTT. \ No newline at end of file diff --git a/html/website.html b/html/website.html index 4e9158e..8757e07 100644 --- a/html/website.html +++ b/html/website.html @@ -38,6 +38,9 @@ + @@ -68,8 +71,8 @@
- - + + -
+
@@ -194,6 +197,10 @@ var socket = new WebSocket("ws://%IPv4%/ws"); + function connect() { + socket = new WebSocket("ws://%IPv4%/ws"); + } + function ping() { var myObj = { "ping": { diff --git a/platformio.ini b/platformio.ini index 6bff14b..09a0f65 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,5 +27,5 @@ lib_deps = https://github.com/bblanchon/ArduinoJson.git ; Don't forget to run this script if you changed the html-files provided in any way -extra_scripts = - pre:processHtml.py \ No newline at end of file +;extra_scripts = +; pre:processHtml.py \ No newline at end of file diff --git a/src/logmessages.h b/src/logmessages.h index 3fe5ff0..2800be7 100644 --- a/src/logmessages.h +++ b/src/logmessages.h @@ -15,6 +15,7 @@ static const char noPlaymodeChangeIfIdle[] PROGMEM = "Playmode kann nicht verän static const char noValidTopic[] PROGMEM = "Kein gültiges Topic"; static const char freePtr[] PROGMEM = "Ptr-Freigabe"; static const char freeMemory[] PROGMEM = "Freier Speicher"; +static const char writeEntryToNvs[] PROGMEM = "Schreibe Eintrag in NVS"; static const char freeMemoryAfterFree[] PROGMEM = "Freier Speicher nach Aufräumen"; static const char releaseMemoryOfOldPlaylist[] PROGMEM = "Gebe Speicher der alten Playlist frei."; static const char dirOrFileDoesNotExist[] PROGMEM = "Datei oder Verzeichnis existiert nicht!"; diff --git a/src/main.cpp b/src/main.cpp index a826f33..6c23319 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -188,7 +188,7 @@ uint8_t buttonDebounceInterval = 50; // Interval in ms to sof uint16_t intervalToLongPress = 700; // Interval in ms to distinguish between short and long press of previous/next-button // Where to store the backup-file -static const char backupFile[] PROGMEM = "/backup.txt"; +static const char backupFile[] PROGMEM = "/backup.txt"; // File is written every time a (new) RFID-assignment via GUI is done // Don't change anything here unless you know what you're doing // HELPER // @@ -255,6 +255,7 @@ char mqtt_server[16] = "192.168.2.43"; // IP-address of MQTT-se char stringDelimiter[] = "#"; // Character used to encapsulate data in linear NVS-strings (don't change) char stringOuterDelimiter[] = "^"; // Character used to encapsulate encapsulated data along with RFID-ID in backup-file + void notFound(AsyncWebServerRequest *request) { request->send(404, "text/plain", "Not found"); } @@ -262,6 +263,10 @@ AsyncWebServer wServer(80); AsyncWebSocket ws("/ws"); AsyncEventSource events("/events"); +static const char backupRecoveryWebsite[] PROGMEM = "

Das Backup-File wird eingespielt...
Zur letzten Seite zurückkehren.

"; +static const char restartWebsite[] PROGMEM = "

Der Tonuino wird neu gestartet...
Zur letzten Seite zurückkehren.

"; + + // Audio/mp3 SPIClass spiSD(HSPI); TaskHandle_t mp3Play; @@ -1188,12 +1193,12 @@ void playAudio(void *parameter) { audio.pauseResume(); trackCommand = 0; loggerNl((char *) FPSTR(cmndPause), LOGLEVEL_INFO); - playProperties.pausePlay = !playProperties.pausePlay; - if (playProperties.saveLastPlayPosition) { + if (playProperties.saveLastPlayPosition && playProperties.pausePlay) { snprintf(logBuf, sizeof(logBuf) / sizeof(logBuf[0]), "Titel wurde bei Position %u pausiert.", audio.getFilePos()); loggerNl(logBuf, LOGLEVEL_INFO); nvsRfidWriteWrapper(playProperties.playRfidTag, *(playProperties.playlist + playProperties.currentTrackNumber), audio.getFilePos(), playProperties.playMode, playProperties.currentTrackNumber, playProperties.numberOfTracks); } + playProperties.pausePlay = !playProperties.pausePlay; continue; case NEXTTRACK: @@ -1731,11 +1736,15 @@ void showLed(void *parameter) { } else if (!playProperties.pausePlay) { // Hue-rainbow leds[led].setHue((uint8_t) (((double) 255 / NUM_LEDS) * led)); } else if (playProperties.pausePlay) { - leds[led] = CRGB::Orange; + leds[led % NUM_LEDS] = CRGB::Orange; + leds[(led+NUM_LEDS/4) % NUM_LEDS] = CRGB::Orange; + leds[(led+NUM_LEDS/2) % NUM_LEDS] = CRGB::Orange; + leds[(led+NUM_LEDS/4*3) % NUM_LEDS] = CRGB::Orange; + break; } } } - } else { // ... but to things a little bit different for Webstream as there's no progress available + } else { // ... but do things a little bit different for Webstream as there's no progress available if (lastSwitchTimestamp == 0 || (millis() - lastSwitchTimestamp >= ledSwitchInterval * 1000) || redrawProgress) { redrawProgress = false; lastSwitchTimestamp = millis(); @@ -2827,20 +2836,8 @@ bool dumpNvsToSd(char *_namespace, char *_destFile) { } -// Handles uploaded files. Still in progress... +// Handles uploaded backup-file and writes valid entries into NVS void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - /*if(!index){ - Serial.printf("UploadStart: %s\n", filename.c_str()); - } - for(size_t i=0; i %s\n", nvsEntry[0].nvsKey, nvsEntry[0].nvsEntry); - delay(30); - /*if (isNumber(nvsEntry[0].nvsKey) && nvsEntry[0].nvsEntry[0] == '#') { - Serial.println("ok"); - }*/ + if (isNumber(nvsEntry[0].nvsKey) && nvsEntry[0].nvsEntry[0] == '#') { + snprintf(logBuf, sizeof(logBuf) / sizeof(logBuf[0]), "%s: %s => %s", (char *) FPSTR(writeEntryToNvs), nvsEntry[0].nvsKey, nvsEntry[0].nvsEntry); + loggerNl(logBuf, LOGLEVEL_NOTICE); + prefsRfid.putString(nvsEntry[0].nvsKey, nvsEntry[0].nvsEntry); + } } } } @@ -3132,11 +3124,11 @@ void setup() { }); wServer.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ - request->send(200); + request->send_P(200, "text/html", backupRecoveryWebsite); }, handleUpload); wServer.on("/restart", HTTP_GET, [] (AsyncWebServerRequest *request) { - request->send(200, "text/html", "

Der Tonuino wird neu gestartet...
Zur letzten Seite zurückkehren.

"); + request->send_P(200, "text/html", restartWebsite); Serial.flush(); ESP.restart(); }); diff --git a/src/websiteMgmt.h b/src/websiteMgmt.h index 8a0fac0..bbda9cb 100644 --- a/src/websiteMgmt.h +++ b/src/websiteMgmt.h @@ -38,6 +38,9 @@ static const char mgtWebsite[] PROGMEM = "\
  • \ Allgemein\
  • \ +
  • \ + NVS-Importer\ +
  • \
  • \ Neustart Tonuino\
  • \ @@ -68,8 +71,8 @@ static const char mgtWebsite[] PROGMEM = "\
    \ \ \ - \ - \ + \ + \ \ \ - \
    \ + \ \
    \ \ @@ -193,6 +196,10 @@ static const char mgtWebsite[] PROGMEM = "\ var okBox = '
    Aktion erfolgreich ausgeführt.
    ';\ \ var socket = new WebSocket(\"ws://%IPv4%/ws\");\ +\ + function connect() {\ + socket = new WebSocket(\"ws://%IPv4%/ws\");\ + }\ \ function ping() {\ var myObj = {\