From 00b8fe03c39889a844f4dbd413b401e99ecc4768 Mon Sep 17 00:00:00 2001 From: Torsten Stauder Date: Sat, 28 Nov 2020 23:29:29 +0100 Subject: [PATCH 01/66] Added: Bat-voltage-visualisation and RFID-recall --- README.md | 6 +- html/website.html | 22 ++++- html/website_EN.html | 22 ++++- src/logmessages.h | 8 +- src/logmessages_EN.h | 8 +- src/main.cpp | 202 ++++++++++++++++++++++++++++++++++--------- src/websiteMgmt.h | 22 ++++- src/websiteMgmt_EN.h | 22 ++++- 8 files changed, 263 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index df72725..5733eb8 100644 --- a/README.md +++ b/README.md @@ -129,9 +129,10 @@ Keep in mind the RFID-lib I used is intended for default-SPI-pins only (SCK, MIS * if Neopixel enabled: set NUM_LEDS to the LED-number of your Neopixel-ring and define the Neopixel-type using `#define CHIPSET` * If you're using Arduino-IDE please make sure to change ESP32's partition-layout to `No OTA (2MB APP/2MB Spiffs)` as otherwise the sketch won't fit into the flash-memory. * Please keep in mind that working SD is mandatory. Unless `SD_NOT_MANDATORY_ENABLE` is not set, Tonuino will never fully start up if SD is not working. Only use `SD_NOT_MANDATORY_ENABLE` for debugging as for normal operational mode, not having SD working doesn't make sense. -* If you want to monitor the battery-voltage, make sure to enable `MEASURE_BATTERY_VOLTAGE`. Use a voltage-divider as voltage of a LiPo is way too high for ESP32 (only 3.3V supported!). For my tests I connected VBat with a serial connection of 130k + 390k resistors (VBat--130k--X--390k--GND). X is the measure-point where to connect the GPIO to. +* If you want to monitor the battery-voltage, make sure to enable `MEASURE_BATTERY_VOLTAGE`. Use a voltage-divider as voltage of a LiPo is way too high for ESP32 (only 3.3V supported!). For my tests I connected VBat with a serial connection of 130k + 390k resistors (VBat--130k--X--390k--GND). X is the measure-point where to connect the GPIO to. Please note: via GUI upper and lower voltage for visualisation of battery-voltage (Neopixel) is available. Additional GUI-configurable values are interval (in minutes) for checking battery voltage and the cut off-voltage below whose a warning is shown via Neopixel. * If you're using a headphone-pcb with a [headphone jack](https://www.conrad.de/de/p/cliff-fcr1295-klinken-steckverbinder-3-5-mm-buchse-einbau-horizontal-polzahl-3-stereo-schwarz-1-st-705830.html) that has a pin to indicate if there's a plug, you can use this signal along with the feature `HEADPHONE_ADJUST_ENABLE` to limit the maximum headphone-voltage automatically. As per default you have to invert this signal and connect it to GPIO22. * Enabling `SHUTDOWN_IF_SD_BOOT_FAILS` is really recommended if you run your Tonuino in battery-mode without having a restart-button exposed to the outside of the Tonuino's enclosure. Because otherwise there's no way to restart your Tonuino and the error-state will remain until battery is empty (or you open the enclosure, hehe). +* Enabling `PLAY_LAST_RFID_AFTER_REBOOT` will tell Tonuino to remember the last RFID-tag played after reboot. So rebooting Tonuino will end up in autoplay. * compile and upload the sketch ## Starting Tonuino-ESP32 first time @@ -216,7 +217,8 @@ Indicates different things. Don't forget configuration of number of LEDs via #de * buttons locked: track-progress-LEDs coloured red * paused: track-progress-LEDs coloured orange * rewind: if single-track-loop is activated a LED-rewind is performed when restarting the given track -* (Optional) Undervoltage: flashes three times red if battery-voltage is too low +* (Optional) Undervoltage: flashes three times red if battery-voltage is too low. This voltage-level can be configured via GUI. +* (Optional) Short press of rotary encoder's button provides battery-voltage visualisation via Neopixel. Upper und lower voltage can be adjusted via GUI. Please note: some Neopixels use a reversed addressing which leads to the 'problem', that all effects are shown counter clockwise. If you want to change that behaviour, just enable `NEOPIXEL_REVERSE_ROTATION`. diff --git a/html/website.html b/html/website.html index a006ca5..fb10745 100644 --- a/html/website.html +++ b/html/website.html @@ -180,6 +180,22 @@ +
+ + +
+
+ + +
+
+ + +
+
+ + +
@@ -271,7 +287,11 @@ mVolHeadphone: document.getElementById('maxVolumeHeadphone').value, iBright: document.getElementById('initBrightness').value, nBright: document.getElementById('nightBrightness').value, - iTime: document.getElementById('inactivityTime').value + iTime: document.getElementById('inactivityTime').value, + vWarning: document.getElementById('warningLowVoltage').value, + vIndLow: document.getElementById('voltageIndicatorLow').value, + vIndHi: document.getElementById('voltageIndicatorHigh').value, + vInt: document.getElementById('voltageCheckInterval').value } }; var myJSON = JSON.stringify(myObj); diff --git a/html/website_EN.html b/html/website_EN.html index 299ae3b..e18731f 100644 --- a/html/website_EN.html +++ b/html/website_EN.html @@ -180,6 +180,22 @@ +
+ + +
+
+ + +
+
+ + +
+
+ + +
@@ -271,7 +287,11 @@ mVolHeadphone: document.getElementById('maxVolumeHeadphone').value, iBright: document.getElementById('initBrightness').value, nBright: document.getElementById('nightBrightness').value, - iTime: document.getElementById('inactivityTime').value + iTime: document.getElementById('inactivityTime').value, + vWarning: document.getElementById('warningLowVoltage').value, + vIndLow: document.getElementById('voltageIndicatorLow').value, + vIndHi: document.getElementById('voltageIndicatorHigh').value, + vInt: document.getElementById('voltageCheckInterval').value } }; var myJSON = JSON.stringify(myObj); diff --git a/src/logmessages.h b/src/logmessages.h index c76fec0..752d580 100644 --- a/src/logmessages.h +++ b/src/logmessages.h @@ -144,4 +144,10 @@ static const char currentVoltageMsg[] PROGMEM = "Aktuelle Batteriespannung"; static const char voltageTooLow[] PROGMEM = "Batteriespannung niedrig"; static const char sdBootFailedDeepsleep[] PROGMEM = "Bootgang wegen SD fehlgeschlagen. Gehe in Deepsleep..."; static const char wifiEnabledAfterRestart[] PROGMEM = "WLAN wird aktiviert."; -static const char wifiDisabledAfterRestart[] PROGMEM = "WLAN wird deaktiviert."; \ No newline at end of file +static const char wifiDisabledAfterRestart[] PROGMEM = "WLAN wird deaktiviert."; +static const char voltageIndicatorLowFromNVS[] PROGMEM = "Unterer Spannungslevel (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; +static const char voltageIndicatorHighFromNVS[] PROGMEM = "Oberer Spannungslevel (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; +static const char voltageCheckIntervalFromNVS[] PROGMEM = "Zyklus für Spannungsmessung (Batterie) fuer Neopixel-Anzeige aus NVS geladen"; +static const char warningLowVoltageFromNVS[] PROGMEM = "Spannungslevel (Batterie) fuer Warnung via Neopixel aus NVS geladen"; +static const char unableToRestoreLastRfidFromNVS[] PROGMEM = "Letzte RFID konnte nicht aus NVS geladen werden"; +static const char restoredLastRfidFromNVS[] PROGMEM = "Letzte RFID wurde aus NVS geladen"; \ No newline at end of file diff --git a/src/logmessages_EN.h b/src/logmessages_EN.h index 84bd1a5..8932895 100644 --- a/src/logmessages_EN.h +++ b/src/logmessages_EN.h @@ -144,4 +144,10 @@ static const char currentVoltageMsg[] PROGMEM = "Current battery-voltage"; static const char voltageTooLow[] PROGMEM = "Low battery-voltage"; static const char sdBootFailedDeepsleep[] PROGMEM = "Failed to boot due to SD. Will go to deepsleep..."; static const char wifiEnabledAfterRestart[] PROGMEM = "WiFi will be enabled."; -static const char wifiDisabledAfterRestart[] PROGMEM = "WiFi will be disabled ."; \ No newline at end of file +static const char wifiDisabledAfterRestart[] PROGMEM = "WiFi will be disabled ."; +static const char voltageIndicatorLowFromNVS[] PROGMEM = "Restored lower voltage-level for Neopixel-display from NVS"; +static const char voltageIndicatorHighFromNVS[] PROGMEM = "Restored upper voltage-level for Neopixel-display from NVS"; +static const char voltageCheckIntervalFromNVS[] PROGMEM = "Restored interval of battery-measurement or Neopixel-display from NVS"; +static const char warningLowVoltageFromNVS[] PROGMEM = "Restored battery-voltage-level for warning via Neopixel from NVS"; +static const char unableToRestoreLastRfidFromNVS[] PROGMEM = "Unable to restore last RFID from NVS"; +static const char restoredLastRfidFromNVS[] PROGMEM = "Restored last RFID from NVS"; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6fe1bc4..831f84f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,8 +7,10 @@ #define HEADPHONE_ADJUST_ENABLE // Used to adjust (lower) volume for optional headphone-pcb (refer maxVolumeSpeaker / maxVolumeHeadphone) //#define SINGLE_SPI_ENABLE // If only one SPI-instance should be used instead of two (not yet working!) #define SHUTDOWN_IF_SD_BOOT_FAILS // Will put ESP to deepsleep if boot fails due to SD. Really recommend this if there's in battery-mode no other way to restart ESP! Interval adjustable via deepsleepTimeAfterBootFails. +#define MEASURE_BATTERY_VOLTAGE // Enables battery-measurement via GPIO (ADC) and voltage-divider +//#define PLAY_LAST_RFID_AFTER_REBOOT // When restarting Tonuino, the last RFID that was active before, is recalled and played + -//#define MEASURE_BATTERY_VOLTAGE // Enables battery-measurement via GPIO (ADC) and voltage-divider //#define SD_NOT_MANDATORY_ENABLE // Only for debugging-purposes: Tonuino will also start without mounted SD-card anyway (will only try once to mount it). Will overwrite SHUTDOWN_IF_SD_BOOT_FAILS! //#define BLUETOOTH_ENABLE // Doesn't work currently (so don't enable) as there's not enough DRAM available @@ -121,12 +123,16 @@ char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all // GPIOs (LEDs) #define LED_PIN 12 // Pin where Neopixel is connected to +// (optional) Default-voltages for battery-monitoring +float warningLowVoltage = 3.4; // If battery-voltage is >= this value, a cyclic warning will be indicated by Neopixel (can be changed via GUI!) +uint8_t voltageCheckInterval = 10; // How of battery-voltage is measured (in minutes) (can be changed via GUI!) +float voltageIndicatorLow = 3.0; // Lower range for Neopixel-voltage-indication (0 leds) (can be changed via GUI!) +float voltageIndicatorHigh = 4.2; // Upper range for Neopixel-voltage-indication (all leds) (can be changed via GUI!) + #ifdef MEASURE_BATTERY_VOLTAGE #define VOLTAGE_READ_PIN 33 // Pin to monitor battery-voltage. Change to 35 if you're using Lolin D32 or Lolin D32 pro uint16_t r1 = 391; // First resistor of voltage-divider (kOhms) (measure exact value with multimeter!) uint8_t r2 = 128; // Second resistor of voltage-divider (kOhms) (measure exact value with multimeter!) - float warningLowVoltage = 3.22; // If battery-voltage is >= this value, a cyclic warning will be indicated by Neopixel - uint8_t voltageCheckInterval = 5; // How of battery-voltage is measured (in minutes) // Internal values float refVoltage = 3.3; // Operation-voltage of ESP32; don't change! @@ -144,6 +150,10 @@ char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all #define COLOR_ORDER GRB #endif +#ifdef PLAY_LAST_RFID_AFTER_REBOOT + bool recoverLastRfid = true; +#endif + // Track-Control #define STOP 1 // Stop play #define PLAY 2 // Start play (currently not used) @@ -269,6 +279,7 @@ bool wifiNeedsRestart = false; bool showLedOk = false; bool showPlaylistProgress = false; bool showRewind = false; + bool showLedVoltage = false; #endif // MQTT #ifdef MQTT_ENABLE @@ -419,6 +430,7 @@ void headphoneVolumeManager(void); bool isNumber(const char *str); void loggerNl(const char *str, const uint8_t logLevel); void logger(const char *str, const uint8_t logLevel); +float measureBatteryVoltage(void); #ifdef MQTT_ENABLE bool publishMqtt(const char *topic, const char *payload, bool retained); #endif @@ -484,13 +496,37 @@ void IRAM_ATTR onTimer() { } +#ifdef PLAY_LAST_RFID_AFTER_REBOOT + void storeLastRfidPlayed(char *_rfid) { + prefsSettings.putString("lastRfid", (String) _rfid); + } + + void recoverLastRfidPlayed(void) { + if (recoverLastRfid) { + recoverLastRfid = false; + String lastRfidPlayed = prefsSettings.getString("lastRfid", "-1"); + if (!lastRfidPlayed.compareTo("-1")) { + loggerNl((char *) FPSTR(unableToRestoreLastRfidFromNVS), LOGLEVEL_INFO); + } else { + char *lastRfid = strdup(lastRfidPlayed.c_str()); + xQueueSend(rfidCardQueue, &lastRfid, 0); + snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredLastRfidFromNVS), lastRfidPlayed.c_str()); + loggerNl(logBuf, LOGLEVEL_INFO); + } + } + } +#endif + // Measures voltage of a battery as per interval or after bootup (after allowing a few seconds to settle down) #ifdef MEASURE_BATTERY_VOLTAGE + float measureBatteryVoltage(void) { + float factor = 1 / ((float) r1/(r1+r2)); + return ((float) analogRead(VOLTAGE_READ_PIN) / maxAnalogValue) * refVoltage * factor; + } + void batteryVoltageTester(void) { if ((millis() - lastVoltageCheckTimestamp >= voltageCheckInterval*60000) || (!lastVoltageCheckTimestamp && millis()>=10000)) { - float factor = 1 / ((float) r1/(r1+r2)); - float voltage = ((float) analogRead(VOLTAGE_READ_PIN) / maxAnalogValue) * refVoltage * factor; - + float voltage = measureBatteryVoltage(); #ifdef NEOPIXEL_ENABLE if (voltage <= warningLowVoltage) { snprintf(logBuf, serialLoglength, "%s: (%.2f V)", (char *) FPSTR(voltageTooLow), voltage); @@ -610,8 +646,18 @@ void doButtonActions(void) { break; case 3: - //gotoSleep = true; - break; + buttons[i].isPressed = false; + #ifdef MEASURE_BATTERY_VOLTAGE + float voltage = measureBatteryVoltage(); + snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(currentVoltageMsg), voltage); + loggerNl(logBuf, LOGLEVEL_INFO); + showLedVoltage = true; + #ifdef MQTT_ENABLE + char vstr[6]; + snprintf(vstr, 6, "%.2f", voltage); + publishMqtt((char *) FPSTR(topicBatteryVoltage), vstr, false); + #endif + #endif } } } @@ -759,7 +805,7 @@ void callback(const char *topic, const byte *payload, uint32_t length) { else if (strcmp_P(topic, topicTrackCmnd) == 0) { char *_rfidId = strdup(receivedString); xQueueSend(rfidCardQueue, &_rfidId, 0); - free(_rfidId); + //free(_rfidId); } // Loudness to change? else if (strcmp_P(topic, topicLoudnessCmnd) == 0) { @@ -1702,7 +1748,6 @@ void showLed(void *parameter) { for (uint8_t led = 0; led < NUM_LEDS; led++) { leds[ledAddress(led)] = CRGB::Red; if (buttons[3].currentState) { - FastLED.clear(); FastLED.show(); delay(5); deepSleepManager(); @@ -1758,6 +1803,40 @@ void showLed(void *parameter) { vTaskDelay(portTICK_RATE_MS * 200); } } + + if (showLedVoltage) { + showLedVoltage = false; + float currentVoltage = measureBatteryVoltage(); + float vDiffIndicatorRange = voltageIndicatorHigh-voltageIndicatorLow; + float vDiffCurrent = currentVoltage-voltageIndicatorLow; + + if (vDiffCurrent < 0) { // If voltage is too low or no battery is connected + showLedError = true; + break; + } else { + uint8_t numLedsToLight = ((float) vDiffCurrent/vDiffIndicatorRange) * NUM_LEDS; + FastLED.clear(); + for (uint8_t led = 0; led < numLedsToLight; led++) { + if (((float) numLedsToLight / NUM_LEDS) >= 0.6) { + leds[ledAddress(led)] = CRGB::Green; + } else if (((float) numLedsToLight / NUM_LEDS) <= 0.6 && ((float) numLedsToLight / NUM_LEDS) >= 0.3) { + leds[ledAddress(led)] = CRGB::Orange; + } else { + leds[ledAddress(led)] = CRGB::Red; + } + FastLED.show(); + vTaskDelay(portTICK_RATE_MS*20); + } + + for (uint8_t i=0; i<=100; i++) { + if (hlastVolume != currentVolume || showLedError || showLedOk || !buttons[3].currentState) { + break; + } + + vTaskDelay(portTICK_RATE_MS*20); + } + } + } #endif if (hlastVolume != currentVolume) { // If volume has been changed @@ -1805,7 +1884,7 @@ void showLed(void *parameter) { leds[ledAddress(i)] = CRGB::Blue; FastLED.show(); #ifdef MEASURE_BATTERY_VOLTAGE - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) { + if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || showLedVoltage || !buttons[3].currentState) { #else if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { #endif @@ -1817,7 +1896,7 @@ void showLed(void *parameter) { for (uint8_t i=0; i<=100; i++) { #ifdef MEASURE_BATTERY_VOLTAGE - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) { + if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || showLedVoltage || !buttons[3].currentState) { #else if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { #endif @@ -1831,7 +1910,7 @@ void showLed(void *parameter) { leds[ledAddress(i)-1] = CRGB::Black; FastLED.show(); #ifdef MEASURE_BATTERY_VOLTAGE - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) { + if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || showLedVoltage || !buttons[3].currentState) { #else if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { #endif @@ -1862,7 +1941,7 @@ void showLed(void *parameter) { FastLED.show(); for (uint8_t i=0; i<=50; i++) { #ifdef MEASURE_BATTERY_VOLTAGE - if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || playProperties.playMode != NO_PLAYLIST || !buttons[3].currentState) { + if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || showLedVoltage || playProperties.playMode != NO_PLAYLIST || !buttons[3].currentState) { #else if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || playProperties.playMode != NO_PLAYLIST || !buttons[3].currentState) { #endif @@ -1901,7 +1980,7 @@ void showLed(void *parameter) { default: // If playlist is active (doesn't matter which type) if (!playProperties.playlistFinished) { #ifdef MEASURE_BATTERY_VOLTAGE - if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || showVoltageWarning || !buttons[3].currentState) { + if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || showVoltageWarning || showLedVoltage || !buttons[3].currentState) { #else if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || !buttons[3].currentState) { #endif @@ -2112,6 +2191,10 @@ void trackQueueDispatcher(const char *_itemToPlay, const uint32_t _lastPlayPos, playProperties.saveLastPlayPosition = false; playProperties.playUntilTrackNumber = 0; + #ifdef PLAY_LAST_RFID_AFTER_REBOOT + storeLastRfidPlayed(currentRfidTagId); + #endif + switch(playProperties.playMode) { case SINGLE_TRACK: { loggerNl((char *) FPSTR(modeSingleTrack), LOGLEVEL_NOTICE); @@ -2823,6 +2906,14 @@ String templateProcessor(const String& templ) { return String(prefsSettings.getUInt("maxVolumeSp", 0)); } else if (templ == "MAX_VOLUME_HEADPHONE") { return String(prefsSettings.getUInt("maxVolumeHp", 0)); + } else if (templ == "WARNING_LOW_VOLTAGE") { + return String(prefsSettings.getFloat("wLowVoltage", warningLowVoltage)); + } else if (templ == "VOLTAGE_INDICATOR_LOW") { + return String(prefsSettings.getFloat("vIndicatorLow", voltageIndicatorLow)); + } else if (templ == "VOLTAGE_INDICATOR_HIGH") { + return String(prefsSettings.getFloat("vIndicatorHigh", voltageIndicatorHigh)); + } else if (templ == "VOLTAGE_CHECK_INTERVAL") { + return String(prefsSettings.getUInt("vCheckIntv", voltageCheckInterval)); } else if (templ == "MQTT_SERVER") { return prefsSettings.getString("mqttServer", "-1"); } else if (templ == "MQTT_ENABLE") { @@ -2875,6 +2966,10 @@ bool processJsonRequest(char *_serialJson) { uint8_t iBright = doc["general"]["iBright"].as(); uint8_t nBright = doc["general"]["nBright"].as(); uint8_t iTime = doc["general"]["iTime"].as(); + float vWarning = doc["general"]["vWarning"].as(); + float vIndLow = doc["general"]["vIndLow"].as(); + float vIndHi = doc["general"]["vIndHi"].as(); + uint8_t vInt = doc["general"]["vInt"].as(); prefsSettings.putUInt("initVolume", iVol); prefsSettings.putUInt("maxVolumeSp", mVolSpeaker); @@ -2882,6 +2977,10 @@ bool processJsonRequest(char *_serialJson) { prefsSettings.putUChar("iLedBrightness", iBright); prefsSettings.putUChar("nLedBrightness", nBright); prefsSettings.putUInt("mInactiviyT", iTime); + prefsSettings.putFloat("wLowVoltage", vWarning); + prefsSettings.putFloat("vIndicatorLow", vIndLow); + prefsSettings.putFloat("vIndicatorHigh", vIndHi); + prefsSettings.putUInt("vCheckIntv", vInt); // Check if settings were written successfully if (prefsSettings.getUInt("initVolume", 0) != iVol || @@ -2889,7 +2988,11 @@ bool processJsonRequest(char *_serialJson) { prefsSettings.getUInt("maxVolumeHp", 0) != mVolHeadphone | prefsSettings.getUChar("iLedBrightness", 0) != iBright || prefsSettings.getUChar("nLedBrightness", 0) != nBright || - prefsSettings.getUInt("mInactiviyT", 0) != iTime) { + prefsSettings.getUInt("mInactiviyT", 0) != iTime || + prefsSettings.getFloat("wLowVoltage", 999.99) != vWarning || + prefsSettings.getFloat("vIndicatorLow", 999.99) != vIndLow || + prefsSettings.getFloat("vIndicatorHigh", 999.99) != vIndHi || + prefsSettings.getUInt("vCheckIntv", 17777) != vInt) { return false; } @@ -3504,6 +3607,45 @@ void setup() { loggerNl(logBuf, LOGLEVEL_INFO); } + #ifdef MEASURE_BATTERY_VOLTAGE + // Get voltages from NVS for Neopixel + float vLowIndicator = prefsSettings.getFloat("vIndicatorLow", 999.99); + if (vLowIndicator <= 999) { + voltageIndicatorLow = vLowIndicator; + snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(voltageIndicatorLowFromNVS), vLowIndicator); + loggerNl(logBuf, LOGLEVEL_INFO); + } else { // preseed if not set + prefsSettings.putFloat("vIndicatorLow", voltageIndicatorLow); + } + + float vHighIndicator = prefsSettings.getFloat("vIndicatorHigh", 999.99); + if (vHighIndicator <= 999) { + voltageIndicatorHigh = vHighIndicator; + snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(voltageIndicatorHighFromNVS), vHighIndicator); + loggerNl(logBuf, LOGLEVEL_INFO); + } else { + prefsSettings.putFloat("vIndicatorHigh", voltageIndicatorHigh); + } + + float vLowWarning = prefsSettings.getFloat("wLowVoltage", 999.99); + if (vLowWarning <= 999) { + warningLowVoltage = vLowWarning; + snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(warningLowVoltageFromNVS), vLowWarning); + loggerNl(logBuf, LOGLEVEL_INFO); + } else { + prefsSettings.putFloat("wLowVoltage", warningLowVoltage); + } + + uint32_t vInterval = prefsSettings.getUInt("vCheckIntv", 17777); + if (vInterval != 17777) { + voltageCheckInterval = vInterval; + snprintf(logBuf, serialLoglength, "%s: %u Minuten", (char *) FPSTR(voltageCheckIntervalFromNVS), vInterval); + loggerNl(logBuf, LOGLEVEL_INFO); + } else { + prefsSettings.putUInt("vCheckIntv", voltageCheckInterval); + } + #endif + // Create 1000Hz-HW-Timer (currently only used for buttons) timerSemaphore = xSemaphoreCreateBinary(); timer = timerBegin(0, 240, true); // Prescaler: CPU-clock in MHz @@ -3559,31 +3701,6 @@ void setup() { lastTimeActiveTimestamp = millis(); // initial set after boot - /*if (wifiManager() == WL_CONNECTED) { - // attach AsyncWebSocket for Mgmt-Interface - ws.onEvent(onWebsocketEvent); - wServer.addHandler(&ws); - - // attach AsyncEventSource - wServer.addHandler(&events); - - wServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send_P(200, "text/html", mgtWebsite, templateProcessor); - }); - - wServer.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ - request->send_P(200, "text/html", backupRecoveryWebsite); - }, handleUpload); - - wServer.on("/restart", HTTP_GET, [] (AsyncWebServerRequest *request) { - request->send_P(200, "text/html", restartWebsite); - Serial.flush(); - ESP.restart(); - }); - - wServer.onNotFound(notFound); - wServer.begin(); - }*/ bootComplete = true; Serial.print(F("Free heap: ")); @@ -3622,6 +3739,9 @@ void loop() { lastTimeActiveTimestamp = millis(); // Re-adjust timer while client is connected to avoid ESP falling asleep } #endif + #ifdef PLAY_LAST_RFID_AFTER_REBOOT + recoverLastRfidPlayed(); + #endif } diff --git a/src/websiteMgmt.h b/src/websiteMgmt.h index faafef7..b89d00b 100644 --- a/src/websiteMgmt.h +++ b/src/websiteMgmt.h @@ -180,6 +180,22 @@ static const char mgtWebsite[] PROGMEM = "\ \ \ \ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ \ \ \ @@ -271,7 +287,11 @@ static const char mgtWebsite[] PROGMEM = "\ mVolHeadphone: document.getElementById('maxVolumeHeadphone').value,\ iBright: document.getElementById('initBrightness').value,\ nBright: document.getElementById('nightBrightness').value,\ - iTime: document.getElementById('inactivityTime').value\ + iTime: document.getElementById('inactivityTime').value,\ + vWarning: document.getElementById('warningLowVoltage').value,\ + vIndLow: document.getElementById('voltageIndicatorLow').value,\ + vIndHi: document.getElementById('voltageIndicatorHigh').value,\ + vInt: document.getElementById('voltageCheckInterval').value\ }\ };\ var myJSON = JSON.stringify(myObj);\ diff --git a/src/websiteMgmt_EN.h b/src/websiteMgmt_EN.h index c814ec7..a46536a 100644 --- a/src/websiteMgmt_EN.h +++ b/src/websiteMgmt_EN.h @@ -180,6 +180,22 @@ static const char mgtWebsite[] PROGMEM = "\ \ \ \ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ \ \ \ @@ -271,7 +287,11 @@ static const char mgtWebsite[] PROGMEM = "\ mVolHeadphone: document.getElementById('maxVolumeHeadphone').value,\ iBright: document.getElementById('initBrightness').value,\ nBright: document.getElementById('nightBrightness').value,\ - iTime: document.getElementById('inactivityTime').value\ + iTime: document.getElementById('inactivityTime').value,\ + vWarning: document.getElementById('warningLowVoltage').value,\ + vIndLow: document.getElementById('voltageIndicatorLow').value,\ + vIndHi: document.getElementById('voltageIndicatorHigh').value,\ + vInt: document.getElementById('voltageCheckInterval').value\ }\ };\ var myJSON = JSON.stringify(myObj);\ From b9024d455b93dcdaed8144825cc772bbede488c6 Mon Sep 17 00:00:00 2001 From: Torsten Stauder Date: Mon, 30 Nov 2020 23:14:45 +0100 Subject: [PATCH 02/66] Sleeptimer-bugfix --- README.md | 2 +- src/main.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5733eb8..b46f221 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Keep in mind the RFID-lib I used is intended for default-SPI-pins only (SCK, MIS * If you want to monitor the battery-voltage, make sure to enable `MEASURE_BATTERY_VOLTAGE`. Use a voltage-divider as voltage of a LiPo is way too high for ESP32 (only 3.3V supported!). For my tests I connected VBat with a serial connection of 130k + 390k resistors (VBat--130k--X--390k--GND). X is the measure-point where to connect the GPIO to. Please note: via GUI upper and lower voltage for visualisation of battery-voltage (Neopixel) is available. Additional GUI-configurable values are interval (in minutes) for checking battery voltage and the cut off-voltage below whose a warning is shown via Neopixel. * If you're using a headphone-pcb with a [headphone jack](https://www.conrad.de/de/p/cliff-fcr1295-klinken-steckverbinder-3-5-mm-buchse-einbau-horizontal-polzahl-3-stereo-schwarz-1-st-705830.html) that has a pin to indicate if there's a plug, you can use this signal along with the feature `HEADPHONE_ADJUST_ENABLE` to limit the maximum headphone-voltage automatically. As per default you have to invert this signal and connect it to GPIO22. * Enabling `SHUTDOWN_IF_SD_BOOT_FAILS` is really recommended if you run your Tonuino in battery-mode without having a restart-button exposed to the outside of the Tonuino's enclosure. Because otherwise there's no way to restart your Tonuino and the error-state will remain until battery is empty (or you open the enclosure, hehe). -* Enabling `PLAY_LAST_RFID_AFTER_REBOOT` will tell Tonuino to remember the last RFID-tag played after reboot. So rebooting Tonuino will end up in autoplay. +* Enabling `PLAY_LAST_RFID_AFTER_REBOOT` will tell Tonuino to remember the last RFID-tag played after next reboot. So rebooting Tonuino will end up in autoplay. * compile and upload the sketch ## Starting Tonuino-ESP32 first time diff --git a/src/main.cpp b/src/main.cpp index 831f84f..f442076 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2343,8 +2343,8 @@ void doRfidCardModifications(const uint32_t mod) { } break; - case SLEEP_TIMER_MOD_15: // Puts/undo uC to sleep after 15 minutes - if (sleepTimer == 15) { + case SLEEP_TIMER_MOD_15: // Enables/disables sleep after 15 minutes + if (sleepTimerStartTimestamp && sleepTimer == 15) { sleepTimerStartTimestamp = 0; #ifdef NEOPIXEL_ENABLE ledBrightness = initialLedBrightness; @@ -2376,8 +2376,8 @@ void doRfidCardModifications(const uint32_t mod) { #endif break; - case SLEEP_TIMER_MOD_30: // Puts/undo uC to sleep after 30 minutes - if (sleepTimer == 30) { + case SLEEP_TIMER_MOD_30: // Enables/disables sleep after 30 minutes + if (sleepTimerStartTimestamp && sleepTimer == 30) { sleepTimerStartTimestamp = 0; #ifdef NEOPIXEL_ENABLE ledBrightness = initialLedBrightness; @@ -2409,8 +2409,8 @@ void doRfidCardModifications(const uint32_t mod) { #endif break; - case SLEEP_TIMER_MOD_60: // Puts/undo uC to sleep after 60 minutes - if (sleepTimer == 60) { + case SLEEP_TIMER_MOD_60: // Enables/disables sleep after 60 minutes + if (sleepTimerStartTimestamp && sleepTimer == 60) { sleepTimerStartTimestamp = 0; #ifdef NEOPIXEL_ENABLE ledBrightness = initialLedBrightness; @@ -2442,8 +2442,8 @@ void doRfidCardModifications(const uint32_t mod) { #endif break; - case SLEEP_TIMER_MOD_120: // Puts/undo uC to sleep after 2 hrs - if (sleepTimer == 120) { + case SLEEP_TIMER_MOD_120: // Enables/disables sleep after 2 hrs + if (sleepTimerStartTimestamp && sleepTimer == 120) { sleepTimerStartTimestamp = 0; #ifdef NEOPIXEL_ENABLE ledBrightness = initialLedBrightness; From bbef6c5ac38063a48ba9bfa69fcd49db8378c263 Mon Sep 17 00:00:00 2001 From: Torsten Stauder Date: Wed, 2 Dec 2020 00:39:01 +0100 Subject: [PATCH 03/66] MInor improvements + documentation --- README.md | 66 +++++++++++++++--------- src/main.cpp | 140 +++++++++++++++++++++++++++++---------------------- 2 files changed, 122 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index b46f221..4598f18 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,19 @@ ## NEWS Currently I'm working on a new Tonuino that is completely based on 3.3V and makes use of an (optional) [headphone-pcb](https://github.com/biologist79/Tonuino-ESP32-I2S/tree/master/PCBs/Headphone%20with%20PCM5102a%20and%20TMD1308). As uC-develboard a Lolin32 is used and it's (optionally) battery-powered. So stay tuned... +## History +[...] +* 11.07.2020: Added support for reversed Neopixel addressing. +* 09.10.2020: mqttUser / mqttPassword can now be configured via webgui. +* 16.10.2020: Added English as supported lanuage. +* 01.11.2020: Added directive `SD_NOT_MANDATORY_ENABLE`: for debugging puposes SD can be bypassed at boot. +* 17.11.2020: Introduced a distinct volume for headphone for optional headphone-PCB. +* 20.11.2020: Added directive `MEASURE_BATTERY_VOLTAGE`: monitoring battery's voltage is now supported. +* 25.11.2020: WiFi can npw be activated/deactivated instantly by pressing two buttons. +* 28.11.2020: Battery's voltage can now be visualized by Neopixel by short-press of rotary encoder's burtton. +* 28.11.2020. Added directive `PLAY_LAST_RFID_AFTER_REBOOT`: Tonuino will recall the last RFID played after reboot. +More to come... + ## Disclaimer This is a **fork** of the popular [Tonuino-project](https://github.com/xfjx/TonUINO) which means, that it only shares the basic concept of controlling a music-player by RFID-tags and buttons. **Said this I want to rule out, that the code-basis is completely different and developed by myself**. So there might be features, that are supported by my fork whereas others are missing or implemented differently. For sure both share that it's non-profit, DIY and developed on [Arduino](https://www.arduino.cc/). @@ -10,7 +23,7 @@ This is a **fork** of the popular [Tonuino-project](https://github.com/xfjx/TonU ## What's different (basically)? The original project makes use of microcontrollers (uC) like Arduino nano (which is the [Microchip AVR-platform](https://de.wikipedia.org/wiki/Microchip_AVR) behind the scenes). Music-decoding is done in hardware using [DFPlayer mini](https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299) which offers a uSD-card-slot and an integrated amp as well. Control of this unit is done by a serial-interconnect with a uC using the API provided. -The core of my implementation is based on the popular [ESP32 by Espressif](https://www.espressif.com/en/products/hardware/esp32/overview). Having WiFi-support out-of-the-box makes it possible to provide further features like an integrated FTP-server (to feed the player with music), smarthome-integration via MQTT and webradio. However, my primary focus was to port the project to a modular base. Said this mp3-decoding is done in software with a dedicated uSD-card-slot and music-output is done via I2S-protocol. I did all my tests on [Adafruit's MAX98357A](https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp/pinouts). Hopefully, not only in theory, other DACs that support I2S can be used as well. +The core of my implementation is based on the popular [ESP32 by Espressif](https://www.espressif.com/en/products/hardware/esp32/overview). Having WiFi-support out-of-the-box makes it possible to provide further features like an integrated FTP-server (to feed the player with music), smarthome-integration via MQTT, webradio and administration via webgui. However, my primary focus was to port the project to a modular base. Said this mp3-decoding is done in software with a dedicated uSD-card-slot and music-output is done via I2S-protocol. I did all my tests on [Adafruit's MAX98357A](https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp/pinouts). Hopefully, not only in theory, other DACs that support I2S can be used as well. ## Basic concept/handling The basic idea of Tonuino (and my fork, respectively) is to provide a way, to use the Arduino-platform for a music-control-concept that supports locally stored music-files instead of being fully cloud-dependend. This basically means that RFID-tags are used to direct a music-player. Even for kids this concept is simple: place an RFID-object (card, character) on top of a box and the music starts to play. Place another RFID-object on it and anything else is played. Simple as that. @@ -31,7 +44,7 @@ The heart of my project is an ESP32 on a [Wemos Lolin32 development-board](https Most of them can be ordered cheaper directly in China. It's just a give an short impression of the hardware; feel free to order where ever you want to. These are not affiliate-links. ## Getting Started -I recommend Microsoft's [Visual Studio Code](https://code.visualstudio.com/) alongside with [Platformio Plugin](https://platformio.org/install/ide?install=vscode). Since my project on Github contains [platformio.ini](platformio.ini), libraries used should be fetched automatically. Please note: if you use another ESP32-develboard (Lolin32 e.g.) you might have to change "env:" in platformio.ini to the corresponding value. Documentation can be found [here](https://docs.platformio.org/en/latest/projectconf.html). After that it might be necessary to adjust the names of the GPIO-pins in the upper #define-section of my code. +I recommend Microsoft's [Visual Studio Code](https://code.visualstudio.com/) or [Atom](https://atom.io/) alongside with [Platformio Plugin](https://platformio.org/install/ide?install=vscode). Since my project on Github contains [platformio.ini](platformio.ini), libraries used should be fetched automatically. Please note: if you use another ESP32-develboard (different to Lolin32) you might have to change "env:" in platformio.ini to the corresponding value. Documentation can be found [here](https://docs.platformio.org/en/latest/projectconf.html). After that it might be necessary to adjust the names of the GPIO-pins in the upper #define-section of my code. In the upper section of main.cpp you can specify the modules that should be compiled into the code. Please note: if MQTT is enabled it's still possible to deactivate it via webgui. @@ -73,14 +86,15 @@ A lot of wiring is necessary to get ESP32-Tonuino working. After my first experi | GND | Neopixel | GND | | | 12 | Neopixel | DI | Might be necessary to use a logic-converter 3.3 => 5V | | 17 | (e.g.) BC337 (via R5) | Base | Don't forget R5! | -| 33 | Voltage-divider / BAT | | Optional: Voltage-divider to monitor battery-voltage | +| 33 | Voltage-divider / BAT | | Optional: voltage-divider to monitor battery-voltage | +| 22 | Headphone jack | | Optional: if pulled to ground, headphone-volume is set | Optionally, GPIO 17 can be used to drive a NPN-transistor (BC337-40) that pulls a p-channel MOSFET (IRF9520) to GND in order to switch on/off 5V-current. Transistor-circuit is described [here](https://dl6gl.de/schalten-mit-transistoren.html): Just have a look at Abb. 4. Values of the resistors I used: R1: 10k, R2: omitted(!), R4: 10k, R5: 4,7k.
-Also tested this successfully for a 3.3V-setup with IRF530NPBF (N-channel MOSFET) and NDP6020P (P-channel MOSFET). Resistor-values: R1: 100k, R2: omitted(!), R4: 100k, R5: 4,7k. A 3.3V-setup is helpful if you want to battery-power your Tonuino and 5V is not available in battery-mode. For example this is the case when using Wemos Lolin32 with only having LiPo connected.
+I also tested this successfully for a 3.3V-setup with IRF530NPBF (N-channel MOSFET) and NDP6020P (P-channel MOSFET). Resistor-values: R1: 100k, R2: omitted(!), R4: 100k, R5: 4,7k. A 3.3V-setup is helpful if you want to battery-power your Tonuino and 5V is not available in battery-mode. For example this is the case when using Wemos Lolin32 with only having LiPo connected.
Advice: When powering a SD-card-reader solely with 3.3V, make sure to use one WITHOUT a voltage regulator. Or at least one with a pin dedicated for 3.3V (bypassing voltage regulator). This is because if 3.3V go through the voltage regulator a small voltage-drop will be introduced, which may lead to SD-malfunction as the resulting voltage is a bit too low. Vice versa if you want to connect your reader solely to 5V, make sure to have one WITH a voltage regulator :-). -## Wiring (1 SPI-instance) [EXPERIMENTAL!] +## Wiring (1 SPI-instance) [EXPERIMENTAL, maybe not working!] Basically the same as using 2 SPI-instances but... In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make sure to use different CS-pins. @@ -117,23 +131,25 @@ In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make su | 12 | Neopixel | DI | Might be necessary to use a logic-converter 3.3 => 5V | | 17 | (e.g.) BC337 (via R5) | Base | Don't forget R5! | | 33 | Voltage-divider / BAT | | Optional: Voltage-divider to monitor battery-voltage | +| 22 | Headphone jack | | Optional: if pulled to ground, headphone-volume is set | ## Wiring (custom) / different pinout When using a develboard with for example SD-card-reader already integrated (Lolin D32 Pro), the pinouts described above my not fit; feel free to change them according your needs. Additionaly some boards may use one or some of the GPIOs I used for their internal purposes and that reason for are maybe not exposed via pin-headers. However, having them exposed doesn't mean they can be used without limits. This is because some GPIOs have to be logical LOW or HIGH at start for example and this is probably not the case when connecting stuff to it. Feel free to adjust the GPIOs proposed by me (but be adviced it could take a while to get it running). If you encounter problems please refer the board's manual first.
-Keep in mind the RFID-lib I used is intended for default-SPI-pins only (SCK, MISO, MOSI). [Here](https://github.com/biologist79/Tonuino-ESP32-I2S/tree/master/Hardware-Plaforms/ESP32-A1S-Audiokit) I described a solution for a board with many GPIOs used internally and a very limited number of GPIOs exposed. That's why I had to use different SPI-GPIOs for RFID as well. Please note I used a slightly modified [RFID-lib](https://github.com/biologist79/Tonuino-ESP32-I2S/tree/master/Hardware-Plaforms/ESP32-A1S-Audiokit/lib/MFRC522) there. +[Here](https://github.com/biologist79/Tonuino-ESP32-I2S/tree/master/Hardware-Plaforms/ESP32-A1S-Audiokit) I described a solution for a board with many GPIOs used internally and a very limited number of GPIOs exposed. That's why I had to use different SPI-GPIOs for RFID as well. Please note I used a slightly modified [RFID-lib](https://github.com/biologist79/Tonuino-ESP32-I2S/tree/master/Hardware-Plaforms/ESP32-A1S-Audiokit/lib/MFRC522) there. ## Prerequisites / tipps * choose if optional modules (MQTT, FTP, Neopixel) should be compiled/enabled * for debugging-purposes serialDebug can be set to ERROR, NOTICE, INFO or DEBUG. -* if MQTT=yes, set the IP of the MQTT-server accordingly and check the MQTT-topics (states and commands) +* if MQTT=yes, set the IP or hostname of the MQTT-server accordingly and check the MQTT-topics (states and commands) * if Neopixel enabled: set NUM_LEDS to the LED-number of your Neopixel-ring and define the Neopixel-type using `#define CHIPSET` * If you're using Arduino-IDE please make sure to change ESP32's partition-layout to `No OTA (2MB APP/2MB Spiffs)` as otherwise the sketch won't fit into the flash-memory. -* Please keep in mind that working SD is mandatory. Unless `SD_NOT_MANDATORY_ENABLE` is not set, Tonuino will never fully start up if SD is not working. Only use `SD_NOT_MANDATORY_ENABLE` for debugging as for normal operational mode, not having SD working doesn't make sense. -* If you want to monitor the battery-voltage, make sure to enable `MEASURE_BATTERY_VOLTAGE`. Use a voltage-divider as voltage of a LiPo is way too high for ESP32 (only 3.3V supported!). For my tests I connected VBat with a serial connection of 130k + 390k resistors (VBat--130k--X--390k--GND). X is the measure-point where to connect the GPIO to. Please note: via GUI upper and lower voltage for visualisation of battery-voltage (Neopixel) is available. Additional GUI-configurable values are interval (in minutes) for checking battery voltage and the cut off-voltage below whose a warning is shown via Neopixel. -* If you're using a headphone-pcb with a [headphone jack](https://www.conrad.de/de/p/cliff-fcr1295-klinken-steckverbinder-3-5-mm-buchse-einbau-horizontal-polzahl-3-stereo-schwarz-1-st-705830.html) that has a pin to indicate if there's a plug, you can use this signal along with the feature `HEADPHONE_ADJUST_ENABLE` to limit the maximum headphone-voltage automatically. As per default you have to invert this signal and connect it to GPIO22. -* Enabling `SHUTDOWN_IF_SD_BOOT_FAILS` is really recommended if you run your Tonuino in battery-mode without having a restart-button exposed to the outside of the Tonuino's enclosure. Because otherwise there's no way to restart your Tonuino and the error-state will remain until battery is empty (or you open the enclosure, hehe). +* Please keep in mind that working SD is mandatory. Unless `SD_NOT_MANDATORY_ENABLE` is not set, Tonuino will never fully start up if SD is not working. Only use `SD_NOT_MANDATORY_ENABLE` for debugging as for normal operational mode, not having SD working doesn't make sense. Even if only webradio-mode is intended, SD would be used to backup RFID-tag-learnings (/backup.txt). +* If you want to monitor battery's voltage, make sure to enable `MEASURE_BATTERY_VOLTAGE`. Use a voltage-divider as voltage of a LiPo is way too high for ESP32 (only 3.3V supported!). For my tests I connected VBat with a serial connection of 130k + 390k resistors (VBat(+)--130k--X--390k--VBat(-)). X is the measure-point where to connect the GPIO to. If using Lolin D32 or Lolin D32 pro, make sure to adjust both values to 100k each and change GPIO to 35 as this is already integrated and wired fixed. +Please note: via GUI upper and lower voltage cut-offs for visualisation of battery-voltage (Neopixel) is available. Additional GUI-configurable values are interval (in minutes) for checking battery voltage and the cut off-voltage below whose a warning is shown via Neopixel. +* If you're using a headphone-pcb with a [headphone jack](https://www.conrad.de/de/p/cliff-fcr1295-klinken-steckverbinder-3-5-mm-buchse-einbau-horizontal-polzahl-3-stereo-schwarz-1-st-705830.html) that has a pin to indicate if there's a plug, you can use this signal along with the feature `HEADPHONE_ADJUST_ENABLE` to limit the maximum headphone-voltage automatically. As per default you have to invert this signal (with a P-channel MOSFET) and connect it to GPIO22. +* Enabling `SHUTDOWN_IF_SD_BOOT_FAILS` is really recommended if you run your Tonuino in battery-mode without having a restart-button exposed to the outside of Tonuino's enclosure. Because otherwise there's no way to restart your Tonuino and the error-state will remain until battery is empty (or you open the enclosure, hehe). * Enabling `PLAY_LAST_RFID_AFTER_REBOOT` will tell Tonuino to remember the last RFID-tag played after next reboot. So rebooting Tonuino will end up in autoplay. -* compile and upload the sketch +* Compile and upload the sketch. ## Starting Tonuino-ESP32 first time After plugging in it takes a few seconds until neopixel indicates that Tonuino is ready (by four (slow) rotating LEDs; white if Wifi enabled and blue if disabled). If uC was not able to connect to WiFi, an access-point (named Tonuino) is opened and after connecting this WiFi, a [configuration-Interface](http://192.168.4.1) is available via webbrowser. Enter WiFI-credentials + the hostname (Tonuio's name), save them and restart the uC. Then reconnect to your "regular" WiFi. Now you're ready to go: start learning RFID-tags via GUI. To reach the GUI enter the IP stated in the serial console or use the hostname. For example if you're using a Fritzbox as router and entered tonuino as hostname in the previous configuration-step, in your webbrowser tonuino.fritz.box should work. After doing rfid-learnings, place your RFID-tag next to the RFID-reader and the music (or whatever else you choosed) should start to play. While the playlist is generated/processed, fast-rotating LEDs are shown to indicate that Tonuino is busy. The more tracks a playlist/directory contains the longer this step will take.
@@ -142,10 +158,10 @@ Please note: hostname can be used to call webgui or FTP-server. I tested it with ## WiFi WiFi is mandatory for webgui, FTP and MQTT. However, WiFi can be temporarily or permanently disabled. There are two ways to do that: * Use a special modification-card that can be configured via webgui -* Press previous-key (and keep pressed!) + press next-button in parallel shortly. Now release both. -This toggles the current WiFi-status which means: if it's currently enabled, it will be disabled instantly and vice versa. Please note: This WiFi-status will remain until you change it again. Having Wifi enabled is indicated in idle-mode (no playlist active) with four white slow rotating LEDs whereas disabled WiFi is represented by those ones colored blue. +* Press previous-key (and keep it pressed) + press next-button in parallel shortly. Now release both. +This toggles the current WiFi-status which means: if it's currently enabled, it will be disabled instantly and vice versa. Please note: this WiFi-status will remain until you change it again, which means, that Tonuino will remember this state after the next reboot. Having Wifi enabled is indicated in idle-mode (no playlist active) with four *white* slow rotating LEDs whereas disabled WiFi is represented by those ones coloured *blue*. ## After Tonuino-ESP32 is connected to your WiFi -After getting Tonuino part of your LAN/WiFi, the 'regular' webgui is available at the IP assigned by your router (or the configured hostname). Using this GUI, you can configure: +After bringing Tonuino part of your LAN/WiFi, the 'regular' webgui is available at the IP assigned by your router (or the configured hostname). Using this GUI, you can configure: * WiFi * Binding between RFID-tag, file/directory/URL and playMode * Binding between RFID-tag and a modification-type @@ -190,7 +206,7 @@ It's not just simply playing music; different playmodes are supported: * webradio (always only one "track") ### Modification RFID-tags -There are special RFID-tags, that don't start music by themself but can modify things. If applied a second time, it's previous action will be reversed. Please note: all sleep-modes do dimming automatically because it's supposed to be used in the evening when going to bed. Well, at least that's my children's indication :-) So first make sure to start the music then use a modification-card in order to apply your desired modification: +There are special RFID-tags, that don't start music by themself but can modify things. If applied a second time, it's previous action/modification will be reversed. Please note: all sleep-modes do dimming (Neopixel) automatically because it's supposed to be used in the evening when going to bed. Well, at least that's my children's indication :-) So first make sure to start the music then use a modification-card in order to apply your desired modification: * lock/unlock all buttons * sleep after 5/30/60/120 minutes * sleep after end of current track @@ -205,20 +221,20 @@ There are special RFID-tags, that don't start music by themself but can modify t ### Neopixel-ring (optional) Indicates different things. Don't forget configuration of number of LEDs via #define NUM_LEDS * While booting: 1/2 LEDs rotating orange -* Unable to mount SD: LEDs flashing red (will remain forever unless SD-card is available) +* Unable to mount SD: LEDs flashing red (will remain forever unless SD-card is available or `SHUTDOWN_IF_SD_BOOT_FAILS` is active) * IDLE: four LEDs slow rotating (white if WiFi enabled; blue if WiFi disabled) -* ERROR: all LEDs flashing red (1x) -* OK: all LEDs flashing green (1x) -* BUSY: violet; four fast rotating LEDs +* ERROR: all LEDs flashing red (1x) if an action was not accepted +* OK: all LEDs flashing green (1x) if an action was accepted +* BUSY: violet; four fast rotating LEDs when generating a playlist * track-progress: rainbow; number of LEDs relative to play-progress * playlist-progress: blue; appears only shortly in playlist-mode with every new track; number of LEDs relative to progress -* volume: green => red-gradient; number of LEDs relative from actual to max volume +* volume: green => red-gradient; number of LEDs relative from current to max volume * switching off: red; circle that grows until long-press-time is reached * buttons locked: track-progress-LEDs coloured red * paused: track-progress-LEDs coloured orange * rewind: if single-track-loop is activated a LED-rewind is performed when restarting the given track * (Optional) Undervoltage: flashes three times red if battery-voltage is too low. This voltage-level can be configured via GUI. -* (Optional) Short press of rotary encoder's button provides battery-voltage visualisation via Neopixel. Upper und lower voltage can be adjusted via GUI. +* (Optional) Short press of rotary encoder's button provides battery-voltage visualisation via Neopixel. Upper und lower voltage cut-offs can be adjusted via GUI. Please note: some Neopixels use a reversed addressing which leads to the 'problem', that all effects are shown counter clockwise. If you want to change that behaviour, just enable `NEOPIXEL_REVERSE_ROTATION`. @@ -232,11 +248,13 @@ Some buttons have different actions if pressed long or short. Minimum duration f * pause/play (short/long): pause/play * rotary encoder (turning): vol +/- * rotary encoder (button long): switch off (only when on) -* rotary encoder (button short): switch on (only when off) -* previous (long; keep pressed) + next (short): toggle WiFi enabled/disabled +* rotary encoder (button short): switch on (when switched off) +* rotary encoder (button short): show battery-voltage (when switched on and `MEASURE_BATTERY_VOLTAGE` is active) +* previous (long; keep pressed) + next (short) + release (both): toggle WiFi enabled/disabled ### Music-play * music starts to play right after valid RFID-tag was applied +* if `PLAY_LAST_RFID_AFTER_REBOOT` is active, Tonuino will remember the last RFID applied => music-autoplay * if a folder should be played that contains many mp3s, the playlist generation can take a few seconds. Please note that a file's name including path cannot exceed 255 characters. * while playlist is generated Neopixel indicates BUSY-mode * after last track was played, Neopixel indicates IDLE-mode diff --git a/src/main.cpp b/src/main.cpp index f442076..e01a2a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -112,7 +112,7 @@ char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all // GPIOs (Rotary encoder) #define DREHENCODER_CLK 34 // If you want to reverse encoder's direction, just switch GPIOs of CLK with DT -#define DREHENCODER_DT 35 // If you want to reverse encoder's direction, just switch GPIOs of CLK with DT +#define DREHENCODER_DT 35 // Info: Lolin D32 / Lolin D32 pro 35 are using 35 for battery-voltage-monitoring! #define DREHENCODER_BUTTON 32 // Button is used to switch Tonuino on and off // GPIOs (Control-buttons) @@ -188,7 +188,7 @@ float voltageIndicatorHigh = 4.2; // Upper range for Neopixel- #define REPEAT_PLAYLIST 110 // Changes active playmode to endless-loop (for a playlist) #define REPEAT_TRACK 111 // Changes active playmode to endless-loop (for a single track) #define DIMM_LEDS_NIGHTMODE 120 // Changes LED-brightness -#define TOGGLE_WIFI_STATUS 130 // Toggles WiFi-status; effective after next reboot +#define TOGGLE_WIFI_STATUS 130 // Toggles WiFi-status // Repeat-Modes #define NO_REPEAT 0 // No repeat @@ -263,7 +263,9 @@ 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"; // File is written every time a (new) RFID-assignment via GUI is done +#ifndef SD_NOT_MANDATORY_ENABLE + static const char backupFile[] PROGMEM = "/backup.txt"; // File is written every time a (new) RFID-assignment via GUI is done +#endif // Don't change anything here unless you know what you're doing // HELPER // @@ -419,7 +421,9 @@ void buttonHandler(); void deepSleepManager(void); void doButtonActions(void); void doRfidCardModifications(const uint32_t mod); -bool dumpNvsToSd(char *_namespace, char *_destFile); +#ifndef SD_NOT_MANDATORY_ENABLE + bool dumpNvsToSd(char *_namespace, char *_destFile); +#endif bool endsWith (const char *str, const char *suf); bool fileValid(const char *_fileItem); void freeMultiCharArray(char **arr, const uint32_t cnt); @@ -497,10 +501,12 @@ void IRAM_ATTR onTimer() { #ifdef PLAY_LAST_RFID_AFTER_REBOOT + // Store last RFID-tag to NVS void storeLastRfidPlayed(char *_rfid) { prefsSettings.putString("lastRfid", (String) _rfid); } + // Get last RFID-tag applied from NVS void recoverLastRfidPlayed(void) { if (recoverLastRfid) { recoverLastRfid = false; @@ -2321,6 +2327,14 @@ void trackQueueDispatcher(const char *_itemToPlay, const uint32_t _lastPlayPos, // Modification-cards can change some settings (e.g. introducing track-looping or sleep after track/playlist). // This function handles them. void doRfidCardModifications(const uint32_t mod) { + #ifdef PLAY_LAST_RFID_AFTER_REBOOT + if (recoverLastRfid) { + recoverLastRfid = false; + // We don't want to remember modification-cards + return; + } + #endif + switch (mod) { case LOCK_BUTTONS_MOD: // Locks/unlocks all buttons lockControls = !lockControls; @@ -2798,7 +2812,7 @@ bool getWifiEnableStatusFromNVS(void) { } -// Writes to NVS whether WiFi should be activated (not effective until next reboot!) +// Writes to NVS whether WiFi should be activated bool writeWifiStatusToNVS(bool wifiStatus) { if (!wifiStatus) { if (prefsSettings.putUInt("enableWifi", 0)) { // disable @@ -3039,7 +3053,9 @@ bool processJsonRequest(char *_serialJson) { if (s.compareTo(rfidString)) { return false; } - dumpNvsToSd("rfidTags", (char *) FPSTR(backupFile)); // Store backup-file every time when a new rfid-tag is programmed + #ifndef SD_NOT_MANDATORY_ENABLE + dumpNvsToSd("rfidTags", (char *) FPSTR(backupFile)); // Store backup-file every time when a new rfid-tag is programmed + #endif } else if (doc.containsKey("rfidAssign")) { const char *_rfidIdAssinId = object["rfidAssign"]["rfidIdMusic"]; @@ -3055,7 +3071,9 @@ bool processJsonRequest(char *_serialJson) { if (s.compareTo(rfidString)) { return false; } - dumpNvsToSd("rfidTags", (char *) FPSTR(backupFile)); // Store backup-file every time when a new rfid-tag is programmed + #ifndef SD_NOT_MANDATORY_ENABLE + dumpNvsToSd("rfidTags", (char *) FPSTR(backupFile)); // Store backup-file every time when a new rfid-tag is programmed + #endif } else if (doc.containsKey("wifiConfig")) { const char *_ssid = object["wifiConfig"]["ssid"]; @@ -3241,69 +3259,71 @@ void webserverStart(void) { // Dumps all RFID-entries from NVS into a file on SD-card -bool dumpNvsToSd(char *_namespace, char *_destFile) { - esp_partition_iterator_t pi; // Iterator for find - const esp_partition_t* nvs; // Pointer to partition struct - esp_err_t result = ESP_OK; - const char* partname = "nvs"; - uint8_t pagenr = 0; // Page number in NVS - uint8_t i; // Index in Entry 0..125 - uint8_t bm; // Bitmap for an entry - uint32_t offset = 0; // Offset in nvs partition - uint8_t namespace_ID; // Namespace ID found - - pi = esp_partition_find ( ESP_PARTITION_TYPE_DATA, // Get partition iterator for - ESP_PARTITION_SUBTYPE_ANY, // this partition - partname ) ; - if (pi) { - nvs = esp_partition_get(pi); // Get partition struct - esp_partition_iterator_release(pi); // Release the iterator - dbgprint ( "Partition %s found, %d bytes", - partname, - nvs->size ) ; - } else { - Serial.printf("Partition %s not found!", partname) ; - return NULL; - } - namespace_ID = FindNsID (nvs, _namespace) ; // Find ID of our namespace in NVS +#ifndef SD_NOT_MANDATORY_ENABLE + bool dumpNvsToSd(char *_namespace, char *_destFile) { + esp_partition_iterator_t pi; // Iterator for find + const esp_partition_t* nvs; // Pointer to partition struct + esp_err_t result = ESP_OK; + const char* partname = "nvs"; + uint8_t pagenr = 0; // Page number in NVS + uint8_t i; // Index in Entry 0..125 + uint8_t bm; // Bitmap for an entry + uint32_t offset = 0; // Offset in nvs partition + uint8_t namespace_ID; // Namespace ID found + + pi = esp_partition_find ( ESP_PARTITION_TYPE_DATA, // Get partition iterator for + ESP_PARTITION_SUBTYPE_ANY, // this partition + partname ) ; + if (pi) { + nvs = esp_partition_get(pi); // Get partition struct + esp_partition_iterator_release(pi); // Release the iterator + dbgprint ( "Partition %s found, %d bytes", + partname, + nvs->size ) ; + } else { + Serial.printf("Partition %s not found!", partname) ; + return NULL; + } + namespace_ID = FindNsID (nvs, _namespace) ; // Find ID of our namespace in NVS - File backupFile = SD.open(_destFile, FILE_WRITE); - if (!backupFile) { - return false; - } - while (offset < nvs->size) { - result = esp_partition_read (nvs, offset, // Read 1 page in nvs partition - &buf, - sizeof(nvs_page)); - if (result != ESP_OK) { - Serial.println(F("Error reading NVS!")); + File backupFile = SD.open(_destFile, FILE_WRITE); + if (!backupFile) { return false; } + while (offset < nvs->size) { + result = esp_partition_read (nvs, offset, // Read 1 page in nvs partition + &buf, + sizeof(nvs_page)); + if (result != ESP_OK) { + Serial.println(F("Error reading NVS!")); + return false; + } - i = 0; + i = 0; - while (i < 126) { - bm = (buf.Bitmap[i/4] >> ((i % 4) * 2 )) & 0x03; // Get bitmap for this entry - if (bm == 2) { - if ((namespace_ID == 0xFF) || // Show all if ID = 0xFF - (buf.Entry[i].Ns == namespace_ID)) { // otherwise just my namespace - if (isNumber(buf.Entry[i].Key)) { - String s = prefsRfid.getString((const char *)buf.Entry[i].Key); - backupFile.printf("%s%s%s%s\n", stringOuterDelimiter, buf.Entry[i].Key, stringOuterDelimiter, s.c_str()); + while (i < 126) { + bm = (buf.Bitmap[i/4] >> ((i % 4) * 2 )) & 0x03; // Get bitmap for this entry + if (bm == 2) { + if ((namespace_ID == 0xFF) || // Show all if ID = 0xFF + (buf.Entry[i].Ns == namespace_ID)) { // otherwise just my namespace + if (isNumber(buf.Entry[i].Key)) { + String s = prefsRfid.getString((const char *)buf.Entry[i].Key); + backupFile.printf("%s%s%s%s\n", stringOuterDelimiter, buf.Entry[i].Key, stringOuterDelimiter, s.c_str()); + } } + i += buf.Entry[i].Span; // Next entry + } else { + i++; } - i += buf.Entry[i].Span; // Next entry - } else { - i++; } + offset += sizeof(nvs_page); // Prepare to read next page in nvs + pagenr++; } - offset += sizeof(nvs_page); // Prepare to read next page in nvs - pagenr++; - } - backupFile.close(); - return true; -} + backupFile.close(); + return true; + } +#endif // Handles uploaded backup-file and writes valid entries into NVS From b099448f034835cd895b47a2074bb4fd5c1b3d2b Mon Sep 17 00:00:00 2001 From: Mario Lukas Date: Thu, 3 Dec 2020 01:43:28 +0100 Subject: [PATCH 04/66] feat (Filebrowser): added file browser to RFID-assignment * added a couple of JavaScript libraries * added functions to main.cpp for file handling * added websocket handller for index file notifications * added webserver route for delivering file index * refactored html notifications, introduced toastr --- html/website.html | 270 +++++++++++++++++++++++++++++++++++++--------- platformio.ini | 30 +++--- src/main.cpp | 221 +++++++++++++++++++++++++++++++++++-- src/websiteMgmt.h | 147 +++++++++++++++++++++++-- 4 files changed, 583 insertions(+), 85 deletions(-) diff --git a/html/website.html b/html/website.html index fb10745..e3c9216 100644 --- a/html/website.html +++ b/html/website.html @@ -5,9 +5,26 @@ + + + + + + +