Browse Source

Merge remote-tracking branch 'upstream/master'

master
kkloesener 5 years ago
parent
commit
225f3f0608
  1. BIN
      PCBs/Headphone with PCM5102a and TMD1308/3D-model_downside.jpeg
  2. BIN
      PCBs/Headphone with PCM5102a and TMD1308/3D-model_upside.jpeg
  3. BIN
      PCBs/Headphone with PCM5102a and TMD1308/Headphone-pcb.pdf
  4. BIN
      PCBs/Headphone with PCM5102a and TMD1308/Headphone-pcb_downside.jpg
  5. BIN
      PCBs/Headphone with PCM5102a and TMD1308/Headphone-pcb_upside.jpg
  6. BIN
      PCBs/Headphone with PCM5102a and TMD1308/Kicad.zip
  7. 6
      PCBs/Headphone with PCM5102a and TMD1308/README.md
  8. 78
      README.md
  9. 28
      html/website.html
  10. 32
      html/website_EN.html
  11. 10
      src/logmessages.h
  12. 10
      src/logmessages_EN.h
  13. 305
      src/main.cpp
  14. 28
      src/websiteMgmt.h
  15. 32
      src/websiteMgmt_EN.h

BIN
PCBs/Headphone with PCM5102a and TMD1308/3D-model_downside.jpeg

After

Width: 1272  |  Height: 883  |  Size: 270 KiB

BIN
PCBs/Headphone with PCM5102a and TMD1308/3D-model_upside.jpeg

After

Width: 1272  |  Height: 883  |  Size: 290 KiB

BIN
PCBs/Headphone with PCM5102a and TMD1308/Headphone-pcb.pdf

BIN
PCBs/Headphone with PCM5102a and TMD1308/Headphone-pcb_downside.jpg

After

Width: 2048  |  Height: 1796  |  Size: 1.1 MiB

BIN
PCBs/Headphone with PCM5102a and TMD1308/Headphone-pcb_upside.jpg

After

Width: 3024  |  Height: 3024  |  Size: 1.6 MiB

BIN
PCBs/Headphone with PCM5102a and TMD1308/Kicad.zip

6
PCBs/Headphone with PCM5102a and TMD1308/README.md

@ -0,0 +1,6 @@
# Headphone-PCB
This is a pcb, that was kindly provided by a user of my Tonuino-fork. It makes use of a DAC named 'PCM5102A' with a TDA1308 as amp. PCM5102A supports I2S, so it can connected in parallel to MAX98357a to the I2S-pins BCLK, LRC and DIN. Of course, it needs 3.3V and GND, too. Please note that SMD-technique is used. <br />
The 6th pin of connector J1 is used to indicate whether there's a plug or not. That means if there is a plug, this pin is pulled to GND and therefore can be used to connect to MAX98357.SD. Doing so will mute MAX's amp and vice versa. Off course you need a special [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) to use this feature. Additionaly this 6th pin can be connected to the ESP32 in order to make use of the feature `HEADPHONE_ADJUST_ENABLE`. Doing so can optionally limit the headphone's maximum volume (configureable via GUI). <br />
## Disclaimer
PCB-circuit is provided 'as is' without warranty. As previously stated it was kindly provided by a user and I only can give limited support to it. However: it runs fine without any problems in my Tonuinos.

78
README.md

@ -1,7 +1,7 @@
# Tonuino based on ESP32 with I2S-DAC-support # Tonuino based on ESP32 with I2S-DAC-support
## NEWS ## NEWS
Currently I'm working on a new Tonuino that is completely based on 3.3V. As uC-develboard a Lolin32 is used and it's (optionally) battery-powered. So stay tuned...
Currently I'm working on a new Tonuino that is completely based on 3.3V and makes use of an (optional) headphone-pcb. As uC-develboard a Lolin32 is used and it's (optionally) battery-powered. So stay tuned...
## Disclaimer ## 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/). 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/).
@ -16,26 +16,27 @@ The core of my implementation is based on the popular [ESP32 by Espressif](https
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. 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.
## Hardware-setup ## Hardware-setup
The heart of my project is an ESP32 on a development-board that more or less looks like [this](https://docs.zerynth.com/latest/official/board.zerynth.nodemcu_esp32/docs/index.html). If ordered in China (Aliexpress, eBay e.g.) it's pretty cheap (around 4€) but even in Europe it's only around 8€. Make sure to install the drivers for the USB/Serial-chip (CP2102 e.g.). If unsure have a look at eBay for "ESP32S" or "Lolin 32".
* [Adafruit's MAX98357A](https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp/pinouts)
* [uSD-card-reader](https://www.amazon.de/AZDelivery-Reader-Speicher-Memory-Arduino/dp/B077MB17JB)
The heart of my project is an ESP32 on a [Wemos Lolin32 development-board](https://www.ebay.de/itm/4MB-Flash-WEMOS-Lolin32-V1-0-0-WIFI-Bluetooth-Card-Based-ESP-32-ESP-WROOM-32/162716855489). If ordered in China (Aliexpress, eBay e.g.) it's pretty cheap (around 4€) but even in Europe it's only around 8€. Make sure to install the drivers for the USB/Serial-chip (CP2102 e.g.). If unsure have a look at eBay or Aliexpress for "Lolin 32".
* [MAX98357A (like Adafruit's)](https://www.ebay.de/itm/MAX98357-Amplifier-Breakout-Interface-I2S-Class-D-Module-For-ESP32-Raspberry-Pi/174319322988)
* [uSD-card-reader 3.3V + 5V](https://www.amazon.de/AZDelivery-Reader-Speicher-Memory-Arduino/dp/B077MB17JB)
* [uSD-card-reader 3.3V only](https://www.ebay.de/itm/Micro-SD-Karten-SD-Card-Modul-SPI-fur-Breadboard-Arduino-Raspberry-Pi/183106778276)
* [RFID-reader](https://www.amazon.de/AZDelivery-Reader-Arduino-Raspberry-gratis/dp/B074S8MRQ7) * [RFID-reader](https://www.amazon.de/AZDelivery-Reader-Arduino-Raspberry-gratis/dp/B074S8MRQ7)
* [RFID-tags](https://www.amazon.de/AZDelivery-Keycard-56MHz-Schlüsselkarte-Karte/dp/B07TVJPTM7) * [RFID-tags](https://www.amazon.de/AZDelivery-Keycard-56MHz-Schlüsselkarte-Karte/dp/B07TVJPTM7)
* [Neopixel-ring](https://www.ebay.de/itm/16Bit-RGB-LED-Ring-WS2812-5V-ahnl-Neopixel-fur-Arduino-Raspberry-Pi/173881828935)
* [Neopixel-ring](https://www.ebay.de/itm/LED-Ring-24-x-5050-RGB-LEDs-WS2812-integrierter-Treiber-NeoPixel-kompatibel/282280571841)
* [Rotary Encoder](https://www.amazon.de/gp/product/B07T3672VK) * [Rotary Encoder](https://www.amazon.de/gp/product/B07T3672VK)
* [Buttons](https://de.aliexpress.com/item/32697109472.html)
* [Buttons](https://de.aliexpress.com/item/32896285438.html)
* [Speaker](https://www.visaton.de/de/produkte/chassiszubehoer/breitband-systeme/fr-7-4-ohm) * [Speaker](https://www.visaton.de/de/produkte/chassiszubehoer/breitband-systeme/fr-7-4-ohm)
* uSD-card: doesn't have to be a super-fast one; uC is limiting the throughput. Tested 32GB without any problems. * uSD-card: doesn't have to be a super-fast one; uC is limiting the throughput. Tested 32GB without any problems.
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. I don't earn money with my links.
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 ## 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/) 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.
In the upper section of main.cpp you can specify the modules that should be compiled into the 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. Please note: if MQTT is enabled it's still possible to deactivate it via webgui.
## Wiring (2 SPI-instances) ## Wiring (2 SPI-instances)
A lot of wiring is necessary to get ESP32-Tonuino working. After my first experiments I soldered the stuff on a board in order to avoid wild-west-cabling. Especially for the interconnect between uC and uSD-card-reader make sure to use short wires (like 10cm or so)!
A lot of wiring is necessary to get ESP32-Tonuino working. After my first experiments I soldered the stuff on a board in order to avoid wild-west-cabling. Especially for the interconnect between uC and uSD-card-reader make sure to use short wires (like 10cm or so)! Important: you can easily connect another I2S-DACs but just connecting them in parallel to the I2S-pins (DIN, BCLK, LRC). This is true for example if you plan to integrate a [line/headphone-pcb](https://www.adafruit.com/product/3678). In general, this runs fine. But unfortunately this board lacks of a headphone jack, that takes note if a plug is inserted or not. Best way is to use 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 that is set to GND, if there's no plug and vice versa. Using for example a MOSFET-circuit, this signal can be inverted in a way, that MAX98357.SD is pulled down to GND if there's a plug. Doing that will turn off the speaker immediately if there's a plug and vice versa. Have a look at the PCB-folder in order to view the detailed solution. Here's an example for such a [headphone-pcb)(https://github.com/biologist79/Tonuino-ESP32-I2S/tree/master/PCBs/Headphone%20with%20PCM5102a%20and%20TMD1308) that makes use of GND.
| ESP32 (GPIO) | Hardware | Pin | Comment | | ESP32 (GPIO) | Hardware | Pin | Comment |
| ------------- | --------------------- | ------ | ------------------------------------------------------------ | | ------------- | --------------------- | ------ | ------------------------------------------------------------ |
@ -47,7 +48,6 @@ A lot of wiring is necessary to get ESP32-Tonuino working. After my first experi
| 14 | SD-reader | SCK | | | 14 | SD-reader | SCK | |
| 17 | RFID-reader | 3.3V | Connect directly to GPIO 17 for power-saving when uC is off | | 17 | RFID-reader | 3.3V | Connect directly to GPIO 17 for power-saving when uC is off |
| GND | RFID-reader | GND | | | GND | RFID-reader | GND | |
| 22 | RFID-reader | RST | |
| 21 | RFID-reader | CS/SDA | | | 21 | RFID-reader | CS/SDA | |
| 23 | RFID-reader | MOSI | | | 23 | RFID-reader | MOSI | |
| 19 | RFID-reader | MISO | | | 19 | RFID-reader | MISO | |
@ -57,6 +57,7 @@ A lot of wiring is necessary to get ESP32-Tonuino working. After my first experi
| 25 | MAX98357 | DIN | | | 25 | MAX98357 | DIN | |
| 27 | MAX98357 | BCLK | | | 27 | MAX98357 | BCLK | |
| 26 | MAX98357 | LRC | | | 26 | MAX98357 | LRC | |
| --- | MAX98357 | SD | Info: if pulled down to GND amp will turn off |
| 34 | Rotary encoder | CLR | Invert CLR with DT if you want to change the direction of RE | | 34 | Rotary encoder | CLR | Invert CLR with DT if you want to change the direction of RE |
| 35 | Rotary encoder | DT | Invert CLR with DT if you want to change the direction of RE | | 35 | Rotary encoder | DT | Invert CLR with DT if you want to change the direction of RE |
| 32 | Rotary encoder | BUTTON | | | 32 | Rotary encoder | BUTTON | |
@ -64,7 +65,7 @@ A lot of wiring is necessary to get ESP32-Tonuino working. After my first experi
| GND | Rotary encoder | GND | | | GND | Rotary encoder | GND | |
| 4 | Button (next) | | | | 4 | Button (next) | | |
| GND | Button (next) | | | | GND | Button (next) | | |
| 33 | Button (previous) | | |
| 2 | Button (previous) | | |
| GND | Button (previous) | | | | GND | Button (previous) | | |
| 5 | Button (pause/play) | | | | 5 | Button (pause/play) | | |
| GND | Button (pause/play) | | | | GND | Button (pause/play) | | |
@ -72,12 +73,15 @@ A lot of wiring is necessary to get ESP32-Tonuino working. After my first experi
| GND | Neopixel | GND | | | GND | Neopixel | GND | |
| 12 | Neopixel | DI | Might be necessary to use a logic-converter 3.3 => 5V | | 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! | | 17 | (e.g.) BC337 (via R5) | Base | Don't forget R5! |
| 33 | Voltage-divider / BAT | | Optional: Voltage-divider to monitor battery-voltage |
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. <br /> 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. <br />
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. <br /> 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. <br />
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 :-). 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!]
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. In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make sure to use different CS-pins.
| ESP32 (GPIO) | Hardware | Pin | Comment | | ESP32 (GPIO) | Hardware | Pin | Comment |
@ -87,7 +91,6 @@ In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make su
| 15 | SD-reader | CS | Don't share with RFID! | | 15 | SD-reader | CS | Don't share with RFID! |
| 17 | RFID-reader | 3.3V | Connect directly to GPIO 17 for power-saving when uC is off | | 17 | RFID-reader | 3.3V | Connect directly to GPIO 17 for power-saving when uC is off |
| GND | RFID-reader | GND | | | GND | RFID-reader | GND | |
| 22 | RFID-reader | RST | |
| 21 | RFID-reader | CS/SDA | Don't share with SD! | | 21 | RFID-reader | CS/SDA | Don't share with SD! |
| 23 | RFID+SD-reader | MOSI | | | 23 | RFID+SD-reader | MOSI | |
| 19 | RFID+SD-reader | MISO | | | 19 | RFID+SD-reader | MISO | |
@ -97,6 +100,7 @@ In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make su
| 25 | MAX98357 | DIN | | | 25 | MAX98357 | DIN | |
| 27 | MAX98357 | BCLK | | | 27 | MAX98357 | BCLK | |
| 26 | MAX98357 | LRC | | | 26 | MAX98357 | LRC | |
| --- | MAX98357 | SD | Info: if pulled down to GND amp will turn off |
| 34 | Rotary encoder | CLR | Invert CLR with DT if you want to change the direction of RE | | 34 | Rotary encoder | CLR | Invert CLR with DT if you want to change the direction of RE |
| 35 | Rotary encoder | DT | Invert CLR with DT if you want to change the direction of RE | | 35 | Rotary encoder | DT | Invert CLR with DT if you want to change the direction of RE |
| 32 | Rotary encoder | BUTTON | | | 32 | Rotary encoder | BUTTON | |
@ -104,7 +108,7 @@ In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make su
| GND | Rotary encoder | GND | | | GND | Rotary encoder | GND | |
| 4 | Button (next) | | | | 4 | Button (next) | | |
| GND | Button (next) | | | | GND | Button (next) | | |
| 33 | Button (previous) | | |
| 2 | Button (previous) | | |
| GND | Button (previous) | | | | GND | Button (previous) | | |
| 5 | Button (pause/play) | | | | 5 | Button (pause/play) | | |
| GND | Button (pause/play) | | | | GND | Button (pause/play) | | |
@ -112,25 +116,27 @@ In this case RFID-reader + SD-reader share SPI's SCK, MISO and MOSI. But make su
| GND | Neopixel | GND | | | GND | Neopixel | GND | |
| 12 | Neopixel | DI | Might be necessary to use a logic-converter 3.3 => 5V | | 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! | | 17 | (e.g.) BC337 (via R5) | Base | Don't forget R5! |
| 33 | Voltage-divider / BAT | | Optional: Voltage-divider to monitor battery-voltage |
## Wiring (custom) / different pinout ## Wiring (custom) / different pinout
When using a develboard with for example SD-card-reader already integrated, the pinouts described above my not fit your needs. Additionaly some boards may use one or some of the GPIOs I used for internal purposes and 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. <br />
When using a develboard with for example SD-card-reader already integrated, the pinouts described above my not fit your needs; 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. <br />
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. 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.
## Prerequisites
## Prerequisites / tipps
* choose if optional modules (MQTT, FTP, Neopixel) should be compiled/enabled * choose if optional modules (MQTT, FTP, Neopixel) should be compiled/enabled
* for debugging-purposes serialDebug can be set to ERROR, NOTICE, INFO or DEBUG. * for debugging-purposes serialDebug can be set to ERROR, NOTICE, INFO or DEBUG.
* make decision, if MQTT should be enabled (enableMqtt) * make decision, if MQTT should be enabled (enableMqtt)
* if yes, set the IP of the MQTT-server accordingly and check the MQTT-topics (states and commands) * if yes, set the IP of the MQTT-server accordingly and check the MQTT-topics (states and commands)
* in setup() RFID-cards can be statically linked to an action/file. Everything is stored in uC's NVS.
* if Neopixel enabled: set NUM_LEDS to the LED-number of your Neopixel-ring and define the Neopixel-type using `#define CHIPSET` * if Neopixel enabled: set NUM_LEDS to the LED-number of your Neopixel-ring and define the Neopixel-type using `#define CHIPSET`
* please note: by using audiobook-mode any playlist-savings will be overwritten with every start unless the RFID-cards in setup() are commented out. Main way to link RFID to an action will be a webservice (still under development)
* 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. * 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`. Make sure to use a voltage-divider as voltage of a LiPo is way too high. 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'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.
* 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.
* compile and upload the sketch * compile and upload the sketch
* 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!
## Starting Tonuino-ESP32 first time ## 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). 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. Enter WiFI-credentials + the hostname (Tonuio's name) and save them and restart the uC. Then reconnect to your "regular" WiFi. Now you're ready to got: place your favourite RFID-tag next to the RFID-reader and the music should start to play. While the playlist is generated, fast-rotating LEDs are shown. The more tracks a playlist/directory contains the longer this step takes. <br >
After plugging in it takes a few seconds until neopixel indicates that Tonuino is ready (by four (slow) rotating white LEDs). 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 the rfid-learning, 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, fast-rotating LEDs are shown to indicate that Tonuino is busy. The more tracks a playlist/directory contains the longer this step takes. <br >
Please note: hostname can be used to call webgui or FTP-server. I tested it with a Fritzbox 7490 and worked fine. Make sure you don't use a name that already exists in you local network (LAN). Please note: hostname can be used to call webgui or FTP-server. I tested it with a Fritzbox 7490 and worked fine. Make sure you don't use a name that already exists in you local network (LAN).
## After Tonuino-ESP32 is connected to your WiFi ## After Tonuino-ESP32 is connected to your WiFi
@ -199,6 +205,7 @@ Indicates different things. Don't forget configuration of number of LEDs via #de
* buttons locked: track-progress-LEDs coloured red * buttons locked: track-progress-LEDs coloured red
* paused: track-progress-LEDs coloured orange * paused: track-progress-LEDs coloured orange
* rewind: if single-track-loop is activated a LED-rewind is performed when restarting the given track * rewind: if single-track-loop is activated a LED-rewind is performed when restarting the given track
* (Optional) Flashes three times red if battery-voltage is too low
Please note: some Neopixels use a reversed addressing which leads to the 'problem', that all effects are shown 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`. counter clockwise. If you want to change that behaviour, just enable `NEOPIXEL_REVERSE_ROTATION`.
@ -219,7 +226,7 @@ Some buttons have different actions if pressed long or short. Minimum duration f
* 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. * 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 * while playlist is generated Neopixel indicates BUSY-mode
* after last track was played, Neopixel indicates IDLE-mode * after last track was played, Neopixel indicates IDLE-mode
* in audiobook-mode, last play-position is remembered (position in actual file and number of track, respectively)
* in audiobook-mode, last play-position is remembered (position in actual file and number of track, respectively) when a new track begins of if pause-button was hit
### Audiobook-mode ### Audiobook-mode
This mode is different from the other ones because the last playposition is saved. Playposition is saved when... This mode is different from the other ones because the last playposition is saved. Playposition is saved when...
@ -237,7 +244,7 @@ After having Tonuino running on your ESP32 in your local WiFi, the webinterface-
* General-configuration (volume, neopixel-brightness, sleep after inactivity) * General-configuration (volume, neopixel-brightness, sleep after inactivity)
### FTP (optional) ### FTP (optional)
In order to avoid exposing uSD-card or disassembling the Tonuino all the time for adding new music, it's possible to transfer music onto the uSD-card using FTP. Please make sure to set the max. number of parallel connections to ONE in your FTP-client. My recommendation is [Filezilla](https://filezilla-project.org/). But don't expect fast transfer, it's only around 145 kB/s and decreases dramatically, if music is played in parallel. Better stop playback then doing a FTP-transfer. Default-user and password are set via `ftpUser` and `ftpPassword`.
In order to avoid exposing uSD-card or disassembling the Tonuino all the time for adding new music, it's possible to transfer music onto the uSD-card using FTP. Please make sure to set the max. number of parallel connections to ONE in your FTP-client. My recommendation is [Filezilla](https://filezilla-project.org/). But don't expect fast data-transfer. Initially it was around 145 kB/s but after modifying ftp-server-lib (changing from 4 kB static-buffer to 16 kB heap-buffer) I saw rates up to 360 kB/s. Please note: if music is played in parallel, this rate decrases dramatically! So better stop playback then doing a FTP-transfer. Default-user and password are set via `ftpUser` and `ftpPassword` but can be changed later via GUI.
### Files (IMPORTANT!) ### Files (IMPORTANT!)
Make sure to not use filenames that contain German 'Umlaute'. I've been told this is also true for mp3's ID3-tags. Make sure to not use filenames that contain German 'Umlaute'. I've been told this is also true for mp3's ID3-tags.
@ -256,3 +263,32 @@ As all assignments between RFID-IDs and actions (playmode, file to play...) is s
## Smarthome (optional) ## 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. For further informations refer the [subfolder](https://github.com/biologist79/Tonuino-ESP32-I2S/tree/master/openHAB). 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. For further informations refer the [subfolder](https://github.com/biologist79/Tonuino-ESP32-I2S/tree/master/openHAB).
## MQTT-topics and their ranges
Feel free to use your own smarthome-environments (instead of openHAB). The MQTT-topics available are described as follows. Please note: if you want to send a command to Tonuino, you have to use a cmnd-topic whereas Tonuino pushes its states back via state-topics. So guess to want to change the volume to 8 you have to send this number via topic-variable `topicLoudnessCmnd`. Immediately after doing to, Tonuino sends a conformation of this command using `topicLoudnessState`.
| topic-variable | range | meaning |
| ----------------------- | --------------- | ------------------------------------------------------------------------------ |
| topicSleepCmnd | 0 or OFF | Power off Tonuino immediately |
| topicSleepState | ON or OFF | Sends Tonuino's current/last state |
| topicTrackCmnd | 12 digits | Set number of RFID-tag which 'emulates' an RFID-tag (e.g. `123789456089`) |
| topicTrackState | 12 digits | Sends number of last RFID-tag applied |
| topicTrackControlCmnd | 1 -> 7 | `1`=stop; `2`=unused!; `3`=play/pause; `4`=next; `5`=prev; `6`=first; `7`=last |
| topicLoudnessCmnd | 0 -> 21 | Set loudness (depends on minVolume / maxVolume) |
| topicLoudnessState | 0 -> 21 | Sends loudness (depends on minVolume / maxVolume |
| topicSleepTimerCmnd | EOP | Power off after end to playlist |
| | EOT | Power off after end of track |
| | EO5T | Power off after end of five tracks |
| | 1 -> 2^32 | Duration in minutes to power off |
| | 0 | Deactivate timer (if active) |
| topicSleepTimerState | various | Sends active timer (`EOP`, `EOT`, `EO5T`, `0`, ...) |
| topicState | Online, Offline | `Online` when powering on, `Offline` when powering off |
| topicCurrentIPv4IP | IPv4-string | Sends Tonuino's IP-address (e.g. `192.168.2.78`) |
| topicLockControlsCmnd | ON, OFF | Set if controls (buttons, rotary encoder) should be locked |
| topicLockControlsState | ON, OFF | Sends if controls (buttons, rotary encoder) are locked |
| topicPlaymodeState | 0 - 10 | Sends current playmode (single track, audiobook...; see playmodes) |
| topicRepeatModeCmnd | 0 - 3 | Set repeat-mode: `0`=no; `1`=track; `2`=playlist; `3`=both |
| topicRepeatModeState | 0 - 3 | Sends repeat-mode |
| topicLedBrightnessCmnd | 0 - 255 | Set brightness of Neopixel |
| topicLedBrightnessState | 0 - 255 | Sends brightness of Neopixel |

28
html/website.html

@ -132,15 +132,12 @@
</label> </label>
</div> </div>
<div class="form-group my-2 col-md-6"> <div class="form-group my-2 col-md-6">
<label for="mqttServer">MQTT-Server (IP-Adresse)</label>
<input type="text" class="form-control" id="mqttServer" pattern="^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$" minlength="7" maxlength="15" placeholder="z.B. 192.168.2.89" name="mqttServer" value="%MQTT_SERVER%">
<div class="invalid-feedback">
Bitte eine gültige IPv4-Adresse eingeben, z.B. 192.168.2.89.
</div>
<label for="mqttServer">MQTT-Server</label>
<input type="text" class="form-control" id="mqttServer" minlength="7" maxlength="%MQTT_SERVER_LENGTH%" placeholder="z.B. 192.168.2.89" name="mqttServer" value="%MQTT_SERVER%">
<label for="mqttUser">MQTT-Benutzername (optional):</label> <label for="mqttUser">MQTT-Benutzername (optional):</label>
<input type="text" class="form-control" id="mqttUser" maxlength="15" placeholder="Benutzername" name="mqttUser" value="%MQTT_USER%">
<input type="text" class="form-control" id="mqttUser" maxlength="%MQTT_USER_LENGTH%" placeholder="Benutzername" name="mqttUser" value="%MQTT_USER%">
<label for="mqttPwd">Passwort (optional):</label> <label for="mqttPwd">Passwort (optional):</label>
<input type="password" class="form-control" id="mqttPwd" maxlength="15" placeholder="Passwort" name="mqttPwd" value="%MQTT_PWD%">
<input type="password" class="form-control" id="mqttPwd" maxlength="%MQTT_PWD_LENGTH%" placeholder="Passwort" name="mqttPwd" value="%MQTT_PWD%">
</div> </div>
<button type="reset" class="btn btn-secondary">Reset</button> <button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Absenden</button> <button type="submit" class="btn btn-primary">Absenden</button>
@ -152,9 +149,9 @@
<form action="#ftpConfig" method="POST" onsubmit="ftpSettings('ftpConfig'); return false"> <form action="#ftpConfig" method="POST" onsubmit="ftpSettings('ftpConfig'); return false">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="ftpUser">FTP-Benutzername:</label> <label for="ftpUser">FTP-Benutzername:</label>
<input type="text" class="form-control" id="ftpUser" maxlength="32" placeholder="Benutzername" name="ftpUser" value="%FTP_USER%" required>
<input type="text" class="form-control" id="ftpUser" maxlength="%FTP_USER_LENGTH%" placeholder="Benutzername" name="ftpUser" value="%FTP_USER%" required>
<label for="pwd">Passwort:</label> <label for="pwd">Passwort:</label>
<input type="password" class="form-control" id="ftpPwd" maxlength="32" placeholder="Passwort" name="ftpPwd" value="%FTP_PWD%" required>
<input type="password" class="form-control" id="ftpPwd" maxlength="%FTP_PWD_LENGTH%" placeholder="Passwort" name="ftpPwd" value="%FTP_PWD%" required>
</div> </div>
<button type="reset" class="btn btn-secondary">Reset</button> <button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Absenden</button> <button type="submit" class="btn btn-primary">Absenden</button>
@ -167,14 +164,16 @@
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="initialVolume">Lautstärke nach dem Einschalten</label> <label for="initialVolume">Lautstärke nach dem Einschalten</label>
<input type="number" min="1" max="21" class="form-control" id="initialVolume" name="initialVolume" value="%INIT_VOLUME%" required> <input type="number" min="1" max="21" class="form-control" id="initialVolume" name="initialVolume" value="%INIT_VOLUME%" required>
<label for="maxVolume">Maximale Lautstärke</label>
<input type="number" min="1" max="21" class="form-control" id="maxVolume" name="maxVolume" value="%MAX_VOLUME%" required>
<label for="maxVolumeSpeaker">Maximale Lautstärke (Lautsprecher)</label>
<input type="number" min="1" max="21" class="form-control" id="maxVolumeSpeaker" name="maxVolumeSpeaker" value="%MAX_VOLUME_SPEAKER%" required>
<label for="maxVolumeHeadphone">Maximale Lautstärke (Kopfhörer)</label>
<input type="number" min="1" max="21" class="form-control" id="maxVolumeHeadphone" name="maxVolumeHeadphone" value="%MAX_VOLUME_HEADPHONE%" required>
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="initBrightness">Neopixel-Helligkeit nach dem Einschalten</label> <label for="initBrightness">Neopixel-Helligkeit nach dem Einschalten</label>
<input type="number" min="0" max="255" class="form-control" id="initBrightness" name="initBrightness" value="%INIT_LED_BRIGHTBESS%" required>
<input type="number" min="0" max="255" class="form-control" id="initBrightness" name="initBrightness" value="%INIT_LED_BRIGHTNESS%" required>
<label for="nightBrightness">Neopixel-Helligkeit im Nachtmodus</label> <label for="nightBrightness">Neopixel-Helligkeit im Nachtmodus</label>
<input type="number" min="0" max="255" class="form-control" id="nightBrightness" name="nightBrightness" value="%NIGHT_LED_BRIGHTBESS%" required>
<input type="number" min="0" max="255" class="form-control" id="nightBrightness" name="nightBrightness" value="%NIGHT_LED_BRIGHTNESS%" required>
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="inactivityTime">Deep-Sleep nach Inaktivität (Minuten)</label> <label for="inactivityTime">Deep-Sleep nach Inaktivität (Minuten)</label>
@ -267,7 +266,8 @@
var myObj = { var myObj = {
"general": { "general": {
iVol: document.getElementById('initialVolume').value, iVol: document.getElementById('initialVolume').value,
mVol: document.getElementById('maxVolume').value,
mVolSpeaker: document.getElementById('maxVolumeSpeaker').value,
mVolHeadphone: document.getElementById('maxVolumeHeadphone').value,
iBright: document.getElementById('initBrightness').value, iBright: document.getElementById('initBrightness').value,
nBright: document.getElementById('nightBrightness').value, nBright: document.getElementById('nightBrightness').value,
iTime: document.getElementById('inactivityTime').value iTime: document.getElementById('inactivityTime').value

32
html/website_EN.html

@ -97,10 +97,10 @@
<h2>RFID-modifications</h2> <h2>RFID-modifications</h2>
<form class="needs-validation" action="#rfidModTags" method="POST" onsubmit="rfidMods('rfidModTags'); return false"> <form class="needs-validation" action="#rfidModTags" method="POST" onsubmit="rfidMods('rfidModTags'); return false">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="rfidIdMod">RFID-chip-ID (12-digits)</label>
<label for="rfidIdMod">RFID-chip-ID (12 digits)</label>
<input type="text" class="form-control" id="rfidIdMod" maxlength="12" pattern="[0-9]{12}" placeholder="%RFID_TAG_ID%" name="rfidIdMod" required> <input type="text" class="form-control" id="rfidIdMod" maxlength="12" pattern="[0-9]{12}" placeholder="%RFID_TAG_ID%" name="rfidIdMod" required>
<div class="invalid-feedback"> <div class="invalid-feedback">
Please enter a 12-digits-number.
Please enter a number with 12 digits.
</div> </div>
<label for="modId">Abspielmodus</label> <label for="modId">Abspielmodus</label>
<select class="form-control" id="modId" name="modId"> <select class="form-control" id="modId" name="modId">
@ -132,15 +132,12 @@
</label> </label>
</div> </div>
<div class="form-group my-2 col-md-6"> <div class="form-group my-2 col-md-6">
<label for="mqttServer">MQTT-server (IP-address)</label>
<input type="text" class="form-control" id="mqttServer" pattern="^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$" minlength="7" maxlength="15" placeholder="z.B. 192.168.2.89" name="mqttServer" value="%MQTT_SERVER%">
<div class="invalid-feedback">
Please enter a valid IPv4-address, e.g. 192.168.2.89.
</div>
<label for="mqttServer">MQTT-server</label>
<input type="text" class="form-control" id="mqttServer" minlength="7" maxlength="%MQTT_SERVER_LENGTH%" placeholder="z.B. 192.168.2.89" name="mqttServer" value="%MQTT_SERVER%">
<label for="mqttUser">MQTT-username (optional):</label> <label for="mqttUser">MQTT-username (optional):</label>
<input type="text" class="form-control" id="mqttUser" maxlength="15" placeholder="Benutzername" name="mqttUser" value="%MQTT_USER%">
<input type="text" class="form-control" id="mqttUser" maxlength="%MQTT_USER_LENGTH%" placeholder="Benutzername" name="mqttUser" value="%MQTT_USER%">
<label for="mqttPwd">Password (optional):</label> <label for="mqttPwd">Password (optional):</label>
<input type="password" class="form-control" id="mqttPwd" maxlength="15" placeholder="Passwort" name="mqttPwd" value="%MQTT_PWD%">
<input type="password" class="form-control" id="mqttPwd" maxlength="%MQTT_PWD_LENGTH%" placeholder="Passwort" name="mqttPwd" value="%MQTT_PWD%">
</div> </div>
<button type="reset" class="btn btn-secondary">Reset</button> <button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
@ -152,9 +149,9 @@
<form action="#ftpConfig" method="POST" onsubmit="ftpSettings('ftpConfig'); return false"> <form action="#ftpConfig" method="POST" onsubmit="ftpSettings('ftpConfig'); return false">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="ftpUser">FTP-username:</label> <label for="ftpUser">FTP-username:</label>
<input type="text" class="form-control" id="ftpUser" maxlength="32" placeholder="Benutzername" name="ftpUser" value="%FTP_USER%" required>
<input type="text" class="form-control" id="ftpUser" maxlength="%FTP_USER_LENGTH%" placeholder="Benutzername" name="ftpUser" value="%FTP_USER%" required>
<label for="pwd">password:</label> <label for="pwd">password:</label>
<input type="password" class="form-control" id="ftpPwd" maxlength="32" placeholder="Passwort" name="ftpPwd" value="%FTP_PWD%" required>
<input type="password" class="form-control" id="ftpPwd" maxlength="%FTP_PWD_LENGTH%" placeholder="Passwort" name="ftpPwd" value="%FTP_PWD%" required>
</div> </div>
<button type="reset" class="btn btn-secondary">Reset</button> <button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Submit</button> <button type="submit" class="btn btn-primary">Submit</button>
@ -167,14 +164,16 @@
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="initialVolume">Volume after start</label> <label for="initialVolume">Volume after start</label>
<input type="number" min="1" max="21" class="form-control" id="initialVolume" name="initialVolume" value="%INIT_VOLUME%" required> <input type="number" min="1" max="21" class="form-control" id="initialVolume" name="initialVolume" value="%INIT_VOLUME%" required>
<label for="maxVolume">Maximum volume</label>
<input type="number" min="1" max="21" class="form-control" id="maxVolume" name="maxVolume" value="%MAX_VOLUME%" required>
<label for="maxVolumeSpeaker">Maximum volume (speaker)</label>
<input type="number" min="1" max="21" class="form-control" id="maxVolumeSpeaker" name="maxVolumeSpeaker" value="%MAX_VOLUME_SPEAKER%" required>
<label for="maxVolumeHeadphone">Maximum volume (headphone)</label>
<input type="number" min="1" max="21" class="form-control" id="maxVolumeHeadphone" name="maxVolumeHeadphone" value="%MAX_VOLUME_HEADPHONE%" required>
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="initBrightness">Neopixel-brightness after start</label> <label for="initBrightness">Neopixel-brightness after start</label>
<input type="number" min="0" max="255" class="form-control" id="initBrightness" name="initBrightness" value="%INIT_LED_BRIGHTBESS%" required>
<input type="number" min="0" max="255" class="form-control" id="initBrightness" name="initBrightness" value="%INIT_LED_BRIGHTNESS%" required>
<label for="nightBrightness">Neopixel-brightness in nightmode</label> <label for="nightBrightness">Neopixel-brightness in nightmode</label>
<input type="number" min="0" max="255" class="form-control" id="nightBrightness" name="nightBrightness" value="%NIGHT_LED_BRIGHTBESS%" required>
<input type="number" min="0" max="255" class="form-control" id="nightBrightness" name="nightBrightness" value="%NIGHT_LED_BRIGHTNESS%" required>
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="inactivityTime">Deepsleep after inactivity (minutes)</label> <label for="inactivityTime">Deepsleep after inactivity (minutes)</label>
@ -267,7 +266,8 @@
var myObj = { var myObj = {
"general": { "general": {
iVol: document.getElementById('initialVolume').value, iVol: document.getElementById('initialVolume').value,
mVol: document.getElementById('maxVolume').value,
mVolSpeaker: document.getElementById('maxVolumeSpeaker').value,
mVolHeadphone: document.getElementById('maxVolumeHeadphone').value,
iBright: document.getElementById('initBrightness').value, iBright: document.getElementById('initBrightness').value,
nBright: document.getElementById('nightBrightness').value, nBright: document.getElementById('nightBrightness').value,
iTime: document.getElementById('inactivityTime').value iTime: document.getElementById('inactivityTime').value

10
src/logmessages.h

@ -118,8 +118,11 @@ static const char restoredMaxInactivityFromNvs[] PROGMEM = "Maximale Inaktivitä
static const char wroteMaxInactivityToNvs[] PROGMEM = "Maximale Inaktivitätszeit wurde ins NVS geschrieben."; static const char wroteMaxInactivityToNvs[] PROGMEM = "Maximale Inaktivitätszeit wurde ins NVS geschrieben.";
static const char restoredInitialLoudnessFromNvs[] PROGMEM = "Initiale Lautstärke wurde aus NVS geladen"; static const char restoredInitialLoudnessFromNvs[] PROGMEM = "Initiale Lautstärke wurde aus NVS geladen";
static const char wroteInitialLoudnessToNvs[] PROGMEM = "Initiale Lautstärke wurde ins NVS geschrieben."; static const char wroteInitialLoudnessToNvs[] PROGMEM = "Initiale Lautstärke wurde ins NVS geschrieben.";
static const char restoredMaxLoudnessFromNvs[] PROGMEM = "Maximale Lautstärke wurde aus NVS geladen";
static const char wroteMaxLoudnessToNvs[] PROGMEM = "Maximale Lautstärke wurde ins NVS geschrieben.";
static const char restoredMaxLoudnessForSpeakerFromNvs[] PROGMEM = "Maximale Lautstärke für Lautsprecher wurde aus NVS geladen";
static const char restoredMaxLoudnessForHeadphoneFromNvs[] PROGMEM = "Maximale Lautstärke für Kopfhörer wurde aus NVS geladen";
static const char wroteMaxLoudnessForSpeakerToNvs[] PROGMEM = "Maximale Lautstärke für Lautsprecher wurde ins NVS geschrieben.";
static const char wroteMaxLoudnessForHeadphoneToNvs[] PROGMEM = "Maximale Lautstärke für Kopfhörer wurde ins NVS geschrieben.";
static const char maxVolumeSet[] PROGMEM = "Maximale Lautstärke wurde gesetzt auf ";
static const char wroteMqttFlagToNvs[] PROGMEM = "MQTT-Flag wurde ins NVS geschrieben."; static const char wroteMqttFlagToNvs[] PROGMEM = "MQTT-Flag wurde ins NVS geschrieben.";
static const char restoredMqttActiveFromNvs[] PROGMEM = "MQTT-Flag (aktiviert) wurde aus NVS geladen"; static const char restoredMqttActiveFromNvs[] PROGMEM = "MQTT-Flag (aktiviert) wurde aus NVS geladen";
static const char restoredMqttDeactiveFromNvs[] PROGMEM = "MQTT-Flag (deaktiviert) wurde aus NVS geladen"; static const char restoredMqttDeactiveFromNvs[] PROGMEM = "MQTT-Flag (deaktiviert) wurde aus NVS geladen";
@ -137,3 +140,6 @@ static const char wifiStaticIpConfigNotFoundInNvs[] PROGMEM = "Statische WLAN-IP
static const char wifiHostnameNotSet[] PROGMEM = "Keine Hostname-Konfiguration im NVS gefunden."; static const char wifiHostnameNotSet[] PROGMEM = "Keine Hostname-Konfiguration im NVS gefunden.";
static const char mqttConnFailed[] PROGMEM = "Verbindung fehlgeschlagen, versuche erneut in Kürze erneut"; static const char mqttConnFailed[] PROGMEM = "Verbindung fehlgeschlagen, versuche erneut in Kürze erneut";
static const char restoredHostnameFromNvs[] PROGMEM = "Hostname aus NVS geladen"; static const char restoredHostnameFromNvs[] PROGMEM = "Hostname aus NVS geladen";
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...";

10
src/logmessages_EN.h

@ -118,8 +118,11 @@ static const char restoredMaxInactivityFromNvs[] PROGMEM = "Restored maximum ina
static const char wroteMaxInactivityToNvs[] PROGMEM = "Stored maximum inactivity-time to NVS."; static const char wroteMaxInactivityToNvs[] PROGMEM = "Stored maximum inactivity-time to NVS.";
static const char restoredInitialLoudnessFromNvs[] PROGMEM = "Restored initial volume from NVS"; static const char restoredInitialLoudnessFromNvs[] PROGMEM = "Restored initial volume from NVS";
static const char wroteInitialLoudnessToNvs[] PROGMEM = "Stored initial volume to NVS."; static const char wroteInitialLoudnessToNvs[] PROGMEM = "Stored initial volume to NVS.";
static const char restoredMaxLoudnessFromNvs[] PROGMEM = "Restored maximum volume from NVS";
static const char wroteMaxLoudnessToNvs[] PROGMEM = "Stored maximum volume to NVS.";
static const char restoredMaxLoudnessForSpeakerFromNvs[] PROGMEM = "Restored maximum volume for speaker from NVS";
static const char restoredMaxLoudnessForHeadphoneFromNvs[] PROGMEM = "Restored maximum volume for headphone from NVS";
static const char wroteMaxLoudnessForSpeakerToNvs[] PROGMEM = "Wrote maximum volume for speaker to NVS.";
static const char wroteMaxLoudnessForHeadphoneToNvs[] PROGMEM = "Wrote maximum volume for headphone to NVS.";
static const char maxVolumeSet[] PROGMEM = "Maximum volume set to ";
static const char wroteMqttFlagToNvs[] PROGMEM = "Stored MQTT-flag to NVS."; static const char wroteMqttFlagToNvs[] PROGMEM = "Stored MQTT-flag to NVS.";
static const char restoredMqttActiveFromNvs[] PROGMEM = "Restored MQTT-flag (enabled) from NVS"; static const char restoredMqttActiveFromNvs[] PROGMEM = "Restored MQTT-flag (enabled) from NVS";
static const char restoredMqttDeactiveFromNvs[] PROGMEM = "Restored MQTT-flag (disabled) from NVS"; static const char restoredMqttDeactiveFromNvs[] PROGMEM = "Restored MQTT-flag (disabled) from NVS";
@ -137,3 +140,6 @@ static const char wifiStaticIpConfigNotFoundInNvs[] PROGMEM = "Unable to find st
static const char wifiHostnameNotSet[] PROGMEM = "Unable to find hostname-configuration to NVS."; static const char wifiHostnameNotSet[] PROGMEM = "Unable to find hostname-configuration to NVS.";
static const char mqttConnFailed[] PROGMEM = "Unable to establish mqtt-connection, trying again..."; static const char mqttConnFailed[] PROGMEM = "Unable to establish mqtt-connection, trying again...";
static const char restoredHostnameFromNvs[] PROGMEM = "Restored hostname from NVS"; static const char restoredHostnameFromNvs[] PROGMEM = "Restored hostname from NVS";
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...";

305
src/main.cpp

@ -4,10 +4,19 @@
//#define NEOPIXEL_ENABLE // Don't forget configuration of NUM_LEDS if enabled //#define NEOPIXEL_ENABLE // Don't forget configuration of NUM_LEDS if enabled
//#define NEOPIXEL_REVERSE_ROTATION // Some Neopixels are adressed/soldered counter-clockwise. This can be configured here. //#define NEOPIXEL_REVERSE_ROTATION // Some Neopixels are adressed/soldered counter-clockwise. This can be configured here.
#define LANGUAGE 1 // 1 = deutsch; 2 = english #define LANGUAGE 1 // 1 = deutsch; 2 = english
<<<<<<< HEAD
// #define HAL 1 // HAL 1 = LoLin32, 2 = AI AudioKit - no need to define when using platformIO // #define HAL 1 // HAL 1 = LoLin32, 2 = AI AudioKit - no need to define when using platformIO
#define MFRC522_BUS 2 // If MFRC522 should be connected to I2C-Port(2) or SPI(1) #define MFRC522_BUS 2 // If MFRC522 should be connected to I2C-Port(2) or SPI(1)
#define DISPLAY_I2C // If external Display via I2C connected - tested with SH1106_128X64_NONAME #define DISPLAY_I2C // If external Display via I2C connected - tested with SH1106_128X64_NONAME
//#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) //#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)
=======
#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 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!
>>>>>>> upstream/master
//#define BLUETOOTH_ENABLE // Doesn't work currently (so don't enable) as there's not enough DRAM available //#define BLUETOOTH_ENABLE // Doesn't work currently (so don't enable) as there's not enough DRAM available
#include <ESP32Encoder.h> #include <ESP32Encoder.h>
@ -83,7 +92,7 @@ char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all
#define SPISD_SCK 14 #define SPISD_SCK 14
// GPIOs (RFID-readercurrentRfidTagId) // GPIOs (RFID-readercurrentRfidTagId)
#define RST_PIN 22
#define RST_PIN 99 // Not necessary but has to be set anyway; so let's use a dummy-number
#define RFID_CS 21 #define RFID_CS 21
#define RFID_MOSI 23 #define RFID_MOSI 23
#define RFID_MISO 19 #define RFID_MISO 19
@ -94,21 +103,51 @@ char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all
#define I2S_BCLK 27 #define I2S_BCLK 27
#define I2S_LRC 26 #define I2S_LRC 26
// GPIO to detect if headphone was plugged in (pulled to GND)
#ifdef HEADPHONE_ADJUST_ENABLE
#define HP_DETECT 22 // Detects if there's a plug in the headphone jack or not
uint16_t headphoneLastDetectionDebounce = 1000; // Debounce-interval in ms when plugging in headphone
// Internal values
bool headphoneLastDetectionState;
uint32_t headphoneLastDetectionTimestamp = 0;
#endif
#ifdef BLUETOOTH_ENABLE
BluetoothA2DPSink a2dp_sink;
#endif
// GPIO used to trigger transistor-circuit / RFID-reader // GPIO used to trigger transistor-circuit / RFID-reader
#define POWER 17 #define POWER 17
// GPIOs (Rotary encoder) // GPIOs (Rotary encoder)
#define DREHENCODER_CLK 34
#define DREHENCODER_DT 35
#define DREHENCODER_BUTTON 32
#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_BUTTON 32 // Button is used to switch Tonuino on and off
// GPIOs (Control-buttons) // GPIOs (Control-buttons)
#define PAUSEPLAY_BUTTON 5 #define PAUSEPLAY_BUTTON 5
#define NEXT_BUTTON 4 #define NEXT_BUTTON 4
#define PREVIOUS_BUTTON 33
#define PREVIOUS_BUTTON 2 // Please note: as of 19.11.2020 changed from 33 to 2
// GPIOs (LEDs) // GPIOs (LEDs)
#define LED_PIN 12
#define LED_PIN 12 // Pin where Neopixel is connected to
#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!
uint16_t maxAnalogValue = 4095; // Highest value given by analogRead(); don't change!
uint32_t lastVoltageCheckTimestamp = 0;
#ifdef NEOPIXEL_ENABLE
bool showVoltageWarning = false;
#endif
#endif
// END HAL 1 // END HAL 1
#elif (HAL == 2) #elif (HAL == 2)
@ -225,10 +264,10 @@ U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* c
#define DIMM_LEDS_NIGHTMODE 120 // Changes LED-brightness #define DIMM_LEDS_NIGHTMODE 120 // Changes LED-brightness
// Repeat-Modes // Repeat-Modes
#define NO_REPEAT 0
#define TRACK 1
#define PLAYLIST 2
#define TRACK_N_PLAYLIST 3
#define NO_REPEAT 0 // No repeat
#define TRACK 1 // Repeat current track (infinite loop)
#define PLAYLIST 2 // Repeat whole playlist (infinite loop)
#define TRACK_N_PLAYLIST 3 // Repeat both (infinite loop)
typedef struct { // Bit field typedef struct { // Bit field
uint8_t playMode: 4; // playMode uint8_t playMode: 4; // playMode
@ -263,6 +302,10 @@ uint8_t initialLedBrightness = 16; // Initial brightness of
uint8_t ledBrightness = initialLedBrightness; uint8_t ledBrightness = initialLedBrightness;
uint8_t nightLedBrightness = 2; // Brightness of Neopixel in nightmode uint8_t nightLedBrightness = 2; // Brightness of Neopixel in nightmode
// Automatic restart
#ifdef SHUTDOWN_IF_SD_BOOT_FAILS
uint32_t deepsleepTimeAfterBootFails = 20; // Automatic restart takes place if boot was not successful after this period (in seconds)
#endif
// MQTT // MQTT
bool enableMqtt = true; bool enableMqtt = true;
#ifdef MQTT_ENABLE #ifdef MQTT_ENABLE
@ -273,15 +316,21 @@ bool enableMqtt = true;
#define RFID_SCAN_INTERVAL 300 // in ms #define RFID_SCAN_INTERVAL 300 // in ms
uint8_t const cardIdSize = 4; // RFID uint8_t const cardIdSize = 4; // RFID
// Volume // Volume
uint8_t maxVolume = 21; // Maximum volume that can be adjusted
uint8_t maxVolume = 21; // Current maximum volume that can be adjusted
uint8_t maxVolumeSpeaker = 21; // Maximum volume that can be adjusted in speaker-mode (default; can be changed later via GUI)
uint8_t minVolume = 0; // Lowest volume that can be adjusted uint8_t minVolume = 0; // Lowest volume that can be adjusted
uint8_t initVolume = 3; // 0...21 (If not found in NVS, this one will be taken)
uint8_t initVolume = 3; // 0...21 (If not found in NVS, this one will be taken) (default; can be changed later via GUI)
#ifdef HEADPHONE_ADJUST_ENABLE
uint8_t maxVolumeHeadphone = 11; // Maximum volume that can be adjusted in headphone-mode (default; can be changed later via GUI)
#endif
// Sleep // Sleep
uint8_t maxInactivityTime = 10; // Time in minutes, after uC is put to deep sleep because of inactivity uint8_t maxInactivityTime = 10; // Time in minutes, after uC is put to deep sleep because of inactivity
uint8_t sleepTimer = 30; // Sleep timer in minutes that can be optionally used (and modified later via MQTT or RFID) uint8_t sleepTimer = 30; // Sleep timer in minutes that can be optionally used (and modified later via MQTT or RFID)
// FTP // FTP
char *ftpUser = strndup((char*) "esp32", 10); // FTP-user
char *ftpPassword = strndup((char*) "esp32", 15); // FTP-password
uint8_t ftpUserLength = 10; // Length will be published n-1 as maxlength to GUI
uint8_t ftpPasswordLength = 15; // Length will be published n-1 as maxlength to GUI
char *ftpUser = strndup((char*) "esp32", ftpUserLength); // FTP-user (default; can be changed later via GUI)
char *ftpPassword = strndup((char*) "esp32", ftpPasswordLength); // FTP-password (default; can be changed later via GUI)
// Button-configuration (change according your needs) // Button-configuration (change according your needs)
uint8_t buttonDebounceInterval = 50; // Interval in ms to software-debounce buttons uint8_t buttonDebounceInterval = 50; // Interval in ms to software-debounce buttons
@ -329,9 +378,15 @@ bool accessPointStarted = false;
// MQTT-configuration // MQTT-configuration
char *mqtt_server = strndup((char*) "192.168.2.43", 16); // IP-address of MQTT-server (if not found in NVS this one will be taken)
char *mqttUser = strndup((char*) "mqtt-user", 16); // MQTT-user
char *mqttPassword = strndup((char*) "mqtt-password", 16); // MQTT-password*/
// Please note: all lengths will be published n-1 as maxlength to GUI
uint8_t mqttServerLength = 32;
uint8_t mqttUserLength = 16;
uint8_t mqttPasswordLength = 16;
// Please note: all of them are defaults that can be changed later via GUI
char *mqtt_server = strndup((char*) "192.168.2.43", mqttServerLength); // IP-address of MQTT-server (if not found in NVS this one will be taken)
char *mqttUser = strndup((char*) "mqtt-user", mqttUserLength); // MQTT-user
char *mqttPassword = strndup((char*) "mqtt-password", mqttPasswordLength); // MQTT-password*/
#ifdef MQTT_ENABLE #ifdef MQTT_ENABLE
#define DEVICE_HOSTNAME "ESP32-Tonuino" // Name that that is used for MQTT #define DEVICE_HOSTNAME "ESP32-Tonuino" // Name that that is used for MQTT
@ -353,6 +408,7 @@ char *mqttPassword = strndup((char*) "mqtt-password", 16); // MQTT-password*/
static const char topicRepeatModeState[] PROGMEM = "State/Tonuino/RepeatMode"; static const char topicRepeatModeState[] PROGMEM = "State/Tonuino/RepeatMode";
static const char topicLedBrightnessCmnd[] PROGMEM = "Cmnd/Tonuino/LedBrightness"; static const char topicLedBrightnessCmnd[] PROGMEM = "Cmnd/Tonuino/LedBrightness";
static const char topicLedBrightnessState[] PROGMEM = "State/Tonuino/LedBrightness"; static const char topicLedBrightnessState[] PROGMEM = "State/Tonuino/LedBrightness";
static const char topicBatteryVoltage[] PROGMEM = "State/Tonuino/Voltage";
#endif #endif
char stringDelimiter[] = "#"; // Character used to encapsulate data in linear NVS-strings (don't change) char stringDelimiter[] = "#"; // Character used to encapsulate data in linear NVS-strings (don't change)
@ -427,6 +483,7 @@ static int arrSortHelper(const void* a, const void* b);
#ifdef MQTT_ENABLE #ifdef MQTT_ENABLE
void callback(const char *topic, const byte *payload, uint32_t length); void callback(const char *topic, const byte *payload, uint32_t length);
#endif #endif
void batteryVoltageTester(void);
void buttonHandler(); void buttonHandler();
void deepSleepManager(void); void deepSleepManager(void);
void doButtonActions(void); void doButtonActions(void);
@ -436,6 +493,7 @@ bool endsWith (const char *str, const char *suf);
bool fileValid(const char *_fileItem); bool fileValid(const char *_fileItem);
void freeMultiCharArray(char **arr, const uint32_t cnt); void freeMultiCharArray(char **arr, const uint32_t cnt);
uint8_t getRepeatMode(void); uint8_t getRepeatMode(void);
void headphoneVolumeManager(void);
bool isNumber(const char *str); bool isNumber(const char *str);
void loggerNl(const char *str, const uint8_t logLevel); void loggerNl(const char *str, const uint8_t logLevel);
void logger(const char *str, const uint8_t logLevel); void logger(const char *str, const uint8_t logLevel);
@ -462,6 +520,7 @@ String templateProcessor(const String& templ);
void trackControlToQueueSender(const uint8_t trackCommand); void trackControlToQueueSender(const uint8_t trackCommand);
void rfidPreferenceLookupHandler (void); void rfidPreferenceLookupHandler (void);
void sendWebsocketData(uint32_t client, uint8_t code); void sendWebsocketData(uint32_t client, uint8_t code);
void setupVolume(void);
void trackQueueDispatcher(const char *_sdFile, const uint32_t _lastPlayPos, const uint32_t _playMode, const uint16_t _trackLastPlayed); void trackQueueDispatcher(const char *_sdFile, const uint32_t _lastPlayPos, const uint32_t _playMode, const uint16_t _trackLastPlayed);
void volumeHandler(const int32_t _minVolume, const int32_t _maxVolume); void volumeHandler(const int32_t _minVolume, const int32_t _maxVolume);
void volumeToQueueSender(const int32_t _newVolume); void volumeToQueueSender(const int32_t _newVolume);
@ -535,6 +594,35 @@ void IRAM_ATTR onTimer() {
} }
// Measures voltage of a battery as per interval or after bootup (after allowing a few seconds to settle down)
#ifdef MEASURE_BATTERY_VOLTAGE
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;
#ifdef NEOPIXEL_ENABLE
if (voltage <= warningLowVoltage) {
snprintf(logBuf, serialLoglength, "%s: (%.2f V)", (char *) FPSTR(voltageTooLow), voltage);
loggerNl(logBuf, LOGLEVEL_ERROR);
showVoltageWarning = true;
}
#endif
#ifdef MQTT_ENABLE
char vstr[6];
snprintf(vstr, 6, "%.2f", voltage);
publishMqtt((char *) FPSTR(topicBatteryVoltage), vstr, false);
#endif
snprintf(logBuf, serialLoglength, "%s: %.2f V", (char *) FPSTR(currentVoltageMsg), voltage);
loggerNl(logBuf, LOGLEVEL_INFO);
//Serial.printf("Spannung: %f\n", voltage);
lastVoltageCheckTimestamp = millis();
}
}
#endif
// If timer-semaphore is set, read buttons (unless controls are locked) // If timer-semaphore is set, read buttons (unless controls are locked)
void buttonHandler() { void buttonHandler() {
if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) { if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
@ -1613,7 +1701,7 @@ void rfidScanner(void *parameter) {
for (uint8_t i=0; i<cardIdSize; i++) { for (uint8_t i=0; i<cardIdSize; i++) {
cardId[i] = mfrc522.uid.uidByte[i]; cardId[i] = mfrc522.uid.uidByte[i];
snprintf(logBuf, sizeof(logBuf)/sizeof(logBuf[0]), "%02x", cardId[i]);
snprintf(logBuf, serialLoglength, "%02x", cardId[i]);
logger(logBuf, LOGLEVEL_NOTICE); logger(logBuf, LOGLEVEL_NOTICE);
n += snprintf (&cardIdString[n], sizeof(cardIdString) / sizeof(cardIdString[0]), "%03d", cardId[i]); n += snprintf (&cardIdString[n], sizeof(cardIdString) / sizeof(cardIdString[0]), "%03d", cardId[i]);
@ -1739,6 +1827,29 @@ void showLed(void *parameter) {
vTaskDelay(portTICK_RATE_MS * 400); vTaskDelay(portTICK_RATE_MS * 400);
} }
#ifdef MEASURE_BATTERY_VOLTAGE
if (showVoltageWarning) { // Flashes red three times if battery-voltage is low
showVoltageWarning = false;
notificationShown = true;
for (uint8_t i=0; i<3; i++) {
FastLED.clear();
for (uint8_t led = 0; led < NUM_LEDS; led++) {
leds[ledAddress(led)] = CRGB::Red;
}
FastLED.show();
vTaskDelay(portTICK_RATE_MS * 200);
FastLED.clear();
for (uint8_t led = 0; led < NUM_LEDS; led++) {
leds[ledAddress(led)] = CRGB::Black;
}
FastLED.show();
vTaskDelay(portTICK_RATE_MS * 200);
}
}
#endif
if (hlastVolume != currentVolume) { // If volume has been changed if (hlastVolume != currentVolume) { // If volume has been changed
uint8_t numLedsToLight = map(currentVolume, 0, maxVolume, 0, NUM_LEDS); uint8_t numLedsToLight = map(currentVolume, 0, maxVolume, 0, NUM_LEDS);
hlastVolume = currentVolume; hlastVolume = currentVolume;
@ -1783,7 +1894,11 @@ void showLed(void *parameter) {
for (uint8_t i=0; i < numLedsToLight; i++) { for (uint8_t i=0; i < numLedsToLight; i++) {
leds[ledAddress(i)] = CRGB::Blue; leds[ledAddress(i)] = CRGB::Blue;
FastLED.show(); FastLED.show();
#ifdef MEASURE_BATTERY_VOLTAGE
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) {
#else
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) {
#endif
break; break;
} else { } else {
vTaskDelay(portTICK_RATE_MS*30); vTaskDelay(portTICK_RATE_MS*30);
@ -1791,7 +1906,11 @@ void showLed(void *parameter) {
} }
for (uint8_t i=0; i<=100; i++) { for (uint8_t i=0; i<=100; i++) {
#ifdef MEASURE_BATTERY_VOLTAGE
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) {
#else
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) {
#endif
break; break;
} else { } else {
vTaskDelay(portTICK_RATE_MS*15); vTaskDelay(portTICK_RATE_MS*15);
@ -1801,7 +1920,11 @@ void showLed(void *parameter) {
for (uint8_t i=numLedsToLight; i>0; i--) { for (uint8_t i=numLedsToLight; i>0; i--) {
leds[ledAddress(i)-1] = CRGB::Black; leds[ledAddress(i)-1] = CRGB::Black;
FastLED.show(); FastLED.show();
#ifdef MEASURE_BATTERY_VOLTAGE
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || showVoltageWarning || !buttons[3].currentState) {
#else
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) { if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || !buttons[3].currentState) {
#endif
break; break;
} else { } else {
vTaskDelay(portTICK_RATE_MS*30); vTaskDelay(portTICK_RATE_MS*30);
@ -1828,7 +1951,11 @@ void showLed(void *parameter) {
} }
FastLED.show(); FastLED.show();
for (uint8_t i=0; i<=50; i++) { 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) {
#else
if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || playProperties.playMode != NO_PLAYLIST || !buttons[3].currentState) { if (hlastVolume != currentVolume || lastLedBrightness != ledBrightness || showLedError || showLedOk || playProperties.playMode != NO_PLAYLIST || !buttons[3].currentState) {
#endif
break; break;
} else { } else {
vTaskDelay(portTICK_RATE_MS * 10); vTaskDelay(portTICK_RATE_MS * 10);
@ -1863,7 +1990,11 @@ void showLed(void *parameter) {
default: // If playlist is active (doesn't matter which type) default: // If playlist is active (doesn't matter which type)
if (!playProperties.playlistFinished) { if (!playProperties.playlistFinished) {
#ifdef MEASURE_BATTERY_VOLTAGE
if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || showVoltageWarning || !buttons[3].currentState) {
#else
if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || !buttons[3].currentState) { if (playProperties.pausePlay != lastPlayState || lockControls != lastLockState || notificationShown || ledBusyShown || volumeChangeShown || !buttons[3].currentState) {
#endif
lastPlayState = playProperties.pausePlay; lastPlayState = playProperties.pausePlay;
lastLockState = lockControls; lastLockState = lockControls;
notificationShown = false; notificationShown = false;
@ -2118,7 +2249,7 @@ void trackQueueDispatcher(const char *_itemToPlay, const uint32_t _lastPlayPos,
} }
case ALL_TRACKS_OF_DIR_SORTED: { case ALL_TRACKS_OF_DIR_SORTED: {
snprintf(logBuf, sizeof(logBuf)/sizeof(logBuf[0]), "%s '%s' ", (char *) FPSTR(modeAllTrackAlphSorted), filename);
snprintf(logBuf, serialLoglength, "%s '%s' ", (char *) FPSTR(modeAllTrackAlphSorted), filename);
loggerNl(logBuf, LOGLEVEL_NOTICE); loggerNl(logBuf, LOGLEVEL_NOTICE);
sortPlaylist((const char**) musicFiles, strtoul(*(musicFiles-1), NULL, 10)); sortPlaylist((const char**) musicFiles, strtoul(*(musicFiles-1), NULL, 10));
#ifdef MQTT_ENABLE #ifdef MQTT_ENABLE
@ -2557,12 +2688,13 @@ void rfidPreferenceLookupHandler (void) {
rfidStatus = xQueueReceive(rfidCardQueue, &rfidTagId, 0); rfidStatus = xQueueReceive(rfidCardQueue, &rfidTagId, 0);
if (rfidStatus == pdPASS) { if (rfidStatus == pdPASS) {
lastTimeActiveTimestamp = millis(); lastTimeActiveTimestamp = millis();
snprintf(logBuf, sizeof(logBuf)/sizeof(logBuf[0]), "%s: %s", (char *) FPSTR(rfidTagReceived), rfidTagId);
free(currentRfidTagId);
currentRfidTagId = strdup(rfidTagId); currentRfidTagId = strdup(rfidTagId);
snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(rfidTagReceived), currentRfidTagId);
sendWebsocketData(0, 10); // Push new rfidTagId to all websocket-clients sendWebsocketData(0, 10); // Push new rfidTagId to all websocket-clients
loggerNl(logBuf, LOGLEVEL_INFO); loggerNl(logBuf, LOGLEVEL_INFO);
String s = prefsRfid.getString(rfidTagId, "-1"); // Try to lookup rfidId in NVS
String s = prefsRfid.getString(currentRfidTagId, "-1"); // Try to lookup rfidId in NVS
if (!s.compareTo("-1")) { if (!s.compareTo("-1")) {
loggerNl((char *) FPSTR(rfidTagUnknownInNvs), LOGLEVEL_ERROR); loggerNl((char *) FPSTR(rfidTagUnknownInNvs), LOGLEVEL_ERROR);
#ifdef NEOPIXEL_ENABLE #ifdef NEOPIXEL_ENABLE
@ -2727,16 +2859,22 @@ String templateProcessor(const String& templ) {
return prefsSettings.getString("ftpuser", "-1"); return prefsSettings.getString("ftpuser", "-1");
} else if (templ == "FTP_PWD") { } else if (templ == "FTP_PWD") {
return prefsSettings.getString("ftppassword", "-1"); return prefsSettings.getString("ftppassword", "-1");
} else if (templ == "INIT_LED_BRIGHTBESS") {
} else if (templ == "FTP_USER_LENGTH") {
return String(ftpUserLength-1);
} else if (templ == "FTP_PWD_LENGTH") {
return String(ftpPasswordLength-1);
} else if (templ == "INIT_LED_BRIGHTNESS") {
return String(prefsSettings.getUChar("iLedBrightness", 0)); return String(prefsSettings.getUChar("iLedBrightness", 0));
} else if (templ == "NIGHT_LED_BRIGHTBESS") {
} else if (templ == "NIGHT_LED_BRIGHTNESS") {
return String(prefsSettings.getUChar("nLedBrightness", 0)); return String(prefsSettings.getUChar("nLedBrightness", 0));
} else if (templ == "MAX_INACTIVITY") { } else if (templ == "MAX_INACTIVITY") {
return String(prefsSettings.getUInt("mInactiviyT", 0)); return String(prefsSettings.getUInt("mInactiviyT", 0));
} else if (templ == "INIT_VOLUME") { } else if (templ == "INIT_VOLUME") {
return String(prefsSettings.getUInt("initVolume", 0)); return String(prefsSettings.getUInt("initVolume", 0));
} else if (templ == "MAX_VOLUME") {
return String(prefsSettings.getUInt("maxVolume", 0));
} else if (templ == "MAX_VOLUME_SPEAKER") {
return String(prefsSettings.getUInt("maxVolumeSp", 0));
} else if (templ == "MAX_VOLUME_HEADPHONE") {
return String(prefsSettings.getUInt("maxVolumeHp", 0));
} else if (templ == "MQTT_SERVER") { } else if (templ == "MQTT_SERVER") {
return prefsSettings.getString("mqttServer", "-1"); return prefsSettings.getString("mqttServer", "-1");
} else if (templ == "MQTT_ENABLE") { } else if (templ == "MQTT_ENABLE") {
@ -2749,6 +2887,12 @@ String templateProcessor(const String& templ) {
return prefsSettings.getString("mqttUser", "-1"); return prefsSettings.getString("mqttUser", "-1");
} else if (templ == "MQTT_PWD") { } else if (templ == "MQTT_PWD") {
return prefsSettings.getString("mqttPassword", "-1"); return prefsSettings.getString("mqttPassword", "-1");
} else if (templ == "MQTT_USER_LENGTH") {
return String(mqttUserLength-1);
} else if (templ == "MQTT_PWD_LENGTH") {
return String(mqttPasswordLength-1);
} else if (templ == "MQTT_SERVER_LENGTH") {
return String(mqttServerLength-1);
} else if (templ == "IPv4") { } else if (templ == "IPv4") {
myIP = WiFi.localIP(); myIP = WiFi.localIP();
snprintf(logBuf, serialLoglength, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]); snprintf(logBuf, serialLoglength, "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]);
@ -2784,20 +2928,23 @@ bool processJsonRequest(char *_serialJson) {
if (doc.containsKey("general")) { if (doc.containsKey("general")) {
uint8_t iVol = doc["general"]["iVol"].as<uint8_t>(); uint8_t iVol = doc["general"]["iVol"].as<uint8_t>();
uint8_t mVol = doc["general"]["mVol"].as<uint8_t>();
uint8_t mVolSpeaker = doc["general"]["mVolSpeaker"].as<uint8_t>();
uint8_t mVolHeadphone = doc["general"]["mVolHeadphone"].as<uint8_t>();
uint8_t iBright = doc["general"]["iBright"].as<uint8_t>(); uint8_t iBright = doc["general"]["iBright"].as<uint8_t>();
uint8_t nBright = doc["general"]["nBright"].as<uint8_t>(); uint8_t nBright = doc["general"]["nBright"].as<uint8_t>();
uint8_t iTime = doc["general"]["iTime"].as<uint8_t>(); uint8_t iTime = doc["general"]["iTime"].as<uint8_t>();
prefsSettings.putUInt("initVolume", iVol); prefsSettings.putUInt("initVolume", iVol);
prefsSettings.putUInt("maxVolume", mVol);
prefsSettings.putUInt("maxVolumeSp", mVolSpeaker);
prefsSettings.putUInt("maxVolumeHp", mVolHeadphone);
prefsSettings.putUChar("iLedBrightness", iBright); prefsSettings.putUChar("iLedBrightness", iBright);
prefsSettings.putUChar("nLedBrightness", nBright); prefsSettings.putUChar("nLedBrightness", nBright);
prefsSettings.putUInt("mInactiviyT", iTime); prefsSettings.putUInt("mInactiviyT", iTime);
// Check if settings were written successfully // Check if settings were written successfully
if (prefsSettings.getUInt("initVolume", 0) != iVol || if (prefsSettings.getUInt("initVolume", 0) != iVol ||
prefsSettings.getUInt("maxVolume", 0) != mVol ||
prefsSettings.getUInt("maxVolumeSp", 0) != mVolSpeaker ||
prefsSettings.getUInt("maxVolumeHp", 0) != mVolHeadphone |
prefsSettings.getUChar("iLedBrightness", 0) != iBright || prefsSettings.getUChar("iLedBrightness", 0) != iBright ||
prefsSettings.getUChar("nLedBrightness", 0) != nBright || prefsSettings.getUChar("nLedBrightness", 0) != nBright ||
prefsSettings.getUInt("mInactiviyT", 0) != iTime) { prefsSettings.getUInt("mInactiviyT", 0) != iTime) {
@ -2968,6 +3115,46 @@ void onWebsocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsE
} }
} }
// Set maxVolume depending on headphone-adjustment is enabled and headphone is/is not connected
void setupVolume(void) {
#ifndef HEADPHONE_ADJUST_ENABLE
maxVolume = maxVolumeSpeaker;
return;
#else
if (digitalRead(HP_DETECT)) {
maxVolume = maxVolumeSpeaker; // 1 if headphone is not connected
} else {
maxVolume = maxVolumeHeadphone; // 0 if headphone is connected (put to GND)
}
snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(maxVolumeSet), maxVolume);
loggerNl(logBuf, LOGLEVEL_INFO);
return;
#endif
}
#ifdef HEADPHONE_ADJUST_ENABLE
void headphoneVolumeManager(void) {
bool currentHeadPhoneDetectionState = digitalRead(HP_DETECT);
if (headphoneLastDetectionState != currentHeadPhoneDetectionState && (millis() - headphoneLastDetectionTimestamp >= headphoneLastDetectionDebounce)) {
if (currentHeadPhoneDetectionState) {
maxVolume = maxVolumeSpeaker;
} else {
maxVolume = maxVolumeHeadphone;
if (currentVolume > maxVolume) {
volumeToQueueSender(maxVolume); // Lower volume for headphone if headphone's maxvolume is exceeded by volume set in speaker-mode
}
}
headphoneLastDetectionState = currentHeadPhoneDetectionState;
headphoneLastDetectionTimestamp = millis();
snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(maxVolumeSet), maxVolume);
loggerNl(logBuf, LOGLEVEL_INFO);
}
}
#endif
bool isNumber(const char *str) { bool isNumber(const char *str) {
byte i = 0; byte i = 0;
@ -3092,6 +3279,7 @@ void handleUpload(AsyncWebServerRequest *request, String filename, size_t index,
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
esp_sleep_enable_ext0_wakeup((gpio_num_t) DREHENCODER_BUTTON, 0);
srand(esp_random()); srand(esp_random());
pinMode(POWER, OUTPUT); pinMode(POWER, OUTPUT);
// digitalWrite(POWER, HIGH); // digitalWrite(POWER, HIGH);
@ -3176,8 +3364,20 @@ if (i2cBus.endTransmission() == 0) {
#ifdef SD_NOT_MANDATORY_ENABLE #ifdef SD_NOT_MANDATORY_ENABLE
break; break;
#endif #endif
#ifdef SHUTDOWN_IF_SD_BOOT_FAILS
if (millis() >= deepsleepTimeAfterBootFails*1000) {
loggerNl((char *) FPSTR(sdBootFailedDeepsleep), LOGLEVEL_ERROR);
esp_deep_sleep_start();
}
#endif
} }
#ifdef HEADPHONE_ADJUST_ENABLE
pinMode(HP_DETECT, INPUT);
headphoneLastDetectionState = digitalRead(HP_DETECT);
#endif
#ifdef BLUETOOTH_ENABLE #ifdef BLUETOOTH_ENABLE
i2s_pin_config_t pin_config = { i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCLK, .bck_io_num = I2S_BCLK,
@ -3241,7 +3441,7 @@ if (i2cBus.endTransmission() == 0) {
prefsSettings.putString("ftpuser", (String) ftpUser); prefsSettings.putString("ftpuser", (String) ftpUser);
loggerNl((char *) FPSTR(wroteFtpUserToNvs), LOGLEVEL_ERROR); loggerNl((char *) FPSTR(wroteFtpUserToNvs), LOGLEVEL_ERROR);
} else { } else {
strncpy(ftpUser, nvsFtpUser.c_str(), sizeof(ftpUser)/sizeof(ftpUser[0]));
strncpy(ftpUser, nvsFtpUser.c_str(), ftpUserLength);
snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredFtpUserFromNvs), nvsFtpUser.c_str()); snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredFtpUserFromNvs), nvsFtpUser.c_str());
loggerNl(logBuf, LOGLEVEL_INFO); loggerNl(logBuf, LOGLEVEL_INFO);
} }
@ -3252,7 +3452,7 @@ if (i2cBus.endTransmission() == 0) {
prefsSettings.putString("ftppassword", (String) ftpPassword); prefsSettings.putString("ftppassword", (String) ftpPassword);
loggerNl((char *) FPSTR(wroteFtpPwdToNvs), LOGLEVEL_ERROR); loggerNl((char *) FPSTR(wroteFtpPwdToNvs), LOGLEVEL_ERROR);
} else { } else {
strncpy(ftpPassword, nvsFtpPassword.c_str(), sizeof(ftpPassword)/sizeof(ftpPassword[0]));
strncpy(ftpPassword, nvsFtpPassword.c_str(), ftpPasswordLength);
snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredFtpPwdFromNvs), nvsFtpPassword.c_str()); snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredFtpPwdFromNvs), nvsFtpPassword.c_str());
loggerNl(logBuf, LOGLEVEL_INFO); loggerNl(logBuf, LOGLEVEL_INFO);
} }
@ -3279,17 +3479,34 @@ if (i2cBus.endTransmission() == 0) {
loggerNl((char *) FPSTR(wroteInitialLoudnessToNvs), LOGLEVEL_ERROR); loggerNl((char *) FPSTR(wroteInitialLoudnessToNvs), LOGLEVEL_ERROR);
} }
// Get maximum volume from NVS
uint32_t nvsMaxVolume = prefsSettings.getUInt("maxVolume", 0);
if (nvsMaxVolume) {
maxVolume = nvsMaxVolume;
snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMaxLoudnessFromNvs), nvsMaxVolume);
// Get maximum volume for speaker from NVS
uint32_t nvsMaxVolumeSpeaker = prefsSettings.getUInt("maxVolumeSp", 0);
if (nvsMaxVolumeSpeaker) {
maxVolumeSpeaker = nvsMaxVolumeSpeaker;
maxVolume = maxVolumeSpeaker;
snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMaxLoudnessForSpeakerFromNvs), nvsMaxVolumeSpeaker);
loggerNl(logBuf, LOGLEVEL_INFO); loggerNl(logBuf, LOGLEVEL_INFO);
} else { } else {
prefsSettings.putUInt("maxVolume", maxVolume);
loggerNl((char *) FPSTR(wroteMaxLoudnessToNvs), LOGLEVEL_ERROR);
prefsSettings.putUInt("maxVolumeSp", nvsMaxVolumeSpeaker);
loggerNl((char *) FPSTR(wroteMaxLoudnessForSpeakerToNvs), LOGLEVEL_ERROR);
} }
#ifdef HEADPHONE_ADJUST_ENABLE
// Get maximum volume for headphone from NVS
uint32_t nvsMaxVolumeHeadphone = prefsSettings.getUInt("maxVolumeHp", 0);
if (nvsMaxVolumeHeadphone) {
maxVolumeHeadphone = nvsMaxVolumeHeadphone;
snprintf(logBuf, serialLoglength, "%s: %u", (char *) FPSTR(restoredMaxLoudnessForHeadphoneFromNvs), nvsMaxVolumeHeadphone);
loggerNl(logBuf, LOGLEVEL_INFO);
} else {
prefsSettings.putUInt("maxVolumeHp", nvsMaxVolumeHeadphone);
loggerNl((char *) FPSTR(wroteMaxLoudnessForHeadphoneToNvs), LOGLEVEL_ERROR);
}
#endif
// Adjust volume depending on headphone is connected and volume-adjustment is enabled
setupVolume();
// Get MQTT-enable from NVS // Get MQTT-enable from NVS
uint8_t nvsEnableMqtt = prefsSettings.getUChar("enableMQTT", 99); uint8_t nvsEnableMqtt = prefsSettings.getUChar("enableMQTT", 99);
switch (nvsEnableMqtt) { switch (nvsEnableMqtt) {
@ -3316,7 +3533,7 @@ if (i2cBus.endTransmission() == 0) {
prefsSettings.putString("mqttServer", (String) mqtt_server); prefsSettings.putString("mqttServer", (String) mqtt_server);
loggerNl((char*) FPSTR(wroteMqttServerToNvs), LOGLEVEL_ERROR); loggerNl((char*) FPSTR(wroteMqttServerToNvs), LOGLEVEL_ERROR);
} else { } else {
strncpy(mqtt_server, nvsMqttServer.c_str(), sizeof(mqtt_server)/sizeof(mqtt_server[0]));
strncpy(mqtt_server, nvsMqttServer.c_str(), mqttServerLength);
snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttServerFromNvs), nvsMqttServer.c_str()); snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttServerFromNvs), nvsMqttServer.c_str());
loggerNl(logBuf, LOGLEVEL_INFO); loggerNl(logBuf, LOGLEVEL_INFO);
} }
@ -3327,7 +3544,7 @@ if (i2cBus.endTransmission() == 0) {
prefsSettings.putString("mqttUser", (String) mqttUser); prefsSettings.putString("mqttUser", (String) mqttUser);
loggerNl((char *) FPSTR(wroteMqttUserToNvs), LOGLEVEL_ERROR); loggerNl((char *) FPSTR(wroteMqttUserToNvs), LOGLEVEL_ERROR);
} else { } else {
strncpy(mqttUser, nvsMqttUser.c_str(), sizeof(mqttUser)/sizeof(mqttUser[0]));
strncpy(mqttUser, nvsMqttUser.c_str(), mqttUserLength);
snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttUserFromNvs), nvsMqttUser.c_str()); snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttUserFromNvs), nvsMqttUser.c_str());
loggerNl(logBuf, LOGLEVEL_INFO); loggerNl(logBuf, LOGLEVEL_INFO);
} }
@ -3338,7 +3555,7 @@ if (i2cBus.endTransmission() == 0) {
prefsSettings.putString("mqttPassword", (String) mqttPassword); prefsSettings.putString("mqttPassword", (String) mqttPassword);
loggerNl((char *) FPSTR(wroteMqttPwdToNvs), LOGLEVEL_ERROR); loggerNl((char *) FPSTR(wroteMqttPwdToNvs), LOGLEVEL_ERROR);
} else { } else {
strncpy(mqttPassword, nvsMqttPassword.c_str(), sizeof(mqttPassword)/sizeof(mqttPassword[0]));
strncpy(mqttPassword, nvsMqttPassword.c_str(), mqttPasswordLength);
snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttPwdFromNvs), nvsMqttPassword.c_str()); snprintf(logBuf, serialLoglength, "%s: %s", (char *) FPSTR(restoredMqttPwdFromNvs), nvsMqttPassword.c_str());
loggerNl(logBuf, LOGLEVEL_INFO); loggerNl(logBuf, LOGLEVEL_INFO);
} }
@ -3372,7 +3589,7 @@ if (i2cBus.endTransmission() == 0) {
); );
esp_sleep_enable_ext0_wakeup((gpio_num_t) DREHENCODER_BUTTON, 0);
//esp_sleep_enable_ext0_wakeup((gpio_num_t) DREHENCODER_BUTTON, 0);
// Activate internal pullups for all buttons // Activate internal pullups for all buttons
pinMode(DREHENCODER_BUTTON, INPUT_PULLUP); pinMode(DREHENCODER_BUTTON, INPUT_PULLUP);
@ -3452,6 +3669,12 @@ if (i2cBus.endTransmission() == 0) {
void loop() { void loop() {
#ifdef HEADPHONE_ADJUST_ENABLE
headphoneVolumeManager();
#endif
#ifdef MEASURE_BATTERY_VOLTAGE
batteryVoltageTester();
#endif
volumeHandler(minVolume, maxVolume); volumeHandler(minVolume, maxVolume);
buttonHandler(); buttonHandler();
doButtonActions(); doButtonActions();

28
src/websiteMgmt.h

@ -132,15 +132,12 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
</label>\ </label>\
</div>\ </div>\
<div class=\"form-group my-2 col-md-6\">\ <div class=\"form-group my-2 col-md-6\">\
<label for=\"mqttServer\">MQTT-Server (IP-Adresse)</label>\
<input type=\"text\" class=\"form-control\" id=\"mqttServer\" pattern=\"^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$\" minlength=\"7\" maxlength=\"15\" placeholder=\"z.B. 192.168.2.89\" name=\"mqttServer\" value=\"%MQTT_SERVER%\">\
<div class=\"invalid-feedback\">\
Bitte eine gültige IPv4-Adresse eingeben, z.B. 192.168.2.89.\
</div>\
<label for=\"mqttServer\">MQTT-Server</label>\
<input type=\"text\" class=\"form-control\" id=\"mqttServer\" minlength=\"7\" maxlength=\"%MQTT_SERVER_LENGTH%\" placeholder=\"z.B. 192.168.2.89\" name=\"mqttServer\" value=\"%MQTT_SERVER%\">\
<label for=\"mqttUser\">MQTT-Benutzername (optional):</label>\ <label for=\"mqttUser\">MQTT-Benutzername (optional):</label>\
<input type=\"text\" class=\"form-control\" id=\"mqttUser\" maxlength=\"15\" placeholder=\"Benutzername\" name=\"mqttUser\" value=\"%MQTT_USER%\">\
<input type=\"text\" class=\"form-control\" id=\"mqttUser\" maxlength=\"%MQTT_USER_LENGTH%\" placeholder=\"Benutzername\" name=\"mqttUser\" value=\"%MQTT_USER%\">\
<label for=\"mqttPwd\">Passwort (optional):</label>\ <label for=\"mqttPwd\">Passwort (optional):</label>\
<input type=\"password\" class=\"form-control\" id=\"mqttPwd\" maxlength=\"15\" placeholder=\"Passwort\" name=\"mqttPwd\" value=\"%MQTT_PWD%\">\
<input type=\"password\" class=\"form-control\" id=\"mqttPwd\" maxlength=\"%MQTT_PWD_LENGTH%\" placeholder=\"Passwort\" name=\"mqttPwd\" value=\"%MQTT_PWD%\">\
</div>\ </div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\ <button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\ <button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
@ -152,9 +149,9 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
<form action=\"#ftpConfig\" method=\"POST\" onsubmit=\"ftpSettings('ftpConfig'); return false\">\ <form action=\"#ftpConfig\" method=\"POST\" onsubmit=\"ftpSettings('ftpConfig'); return false\">\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"ftpUser\">FTP-Benutzername:</label>\ <label for=\"ftpUser\">FTP-Benutzername:</label>\
<input type=\"text\" class=\"form-control\" id=\"ftpUser\" maxlength=\"32\" placeholder=\"Benutzername\" name=\"ftpUser\" value=\"%FTP_USER%\" required>\
<input type=\"text\" class=\"form-control\" id=\"ftpUser\" maxlength=\"%FTP_USER_LENGTH%\" placeholder=\"Benutzername\" name=\"ftpUser\" value=\"%FTP_USER%\" required>\
<label for=\"pwd\">Passwort:</label>\ <label for=\"pwd\">Passwort:</label>\
<input type=\"password\" class=\"form-control\" id=\"ftpPwd\" maxlength=\"32\" placeholder=\"Passwort\" name=\"ftpPwd\" value=\"%FTP_PWD%\" required>\
<input type=\"password\" class=\"form-control\" id=\"ftpPwd\" maxlength=\"%FTP_PWD_LENGTH%\" placeholder=\"Passwort\" name=\"ftpPwd\" value=\"%FTP_PWD%\" required>\
</div>\ </div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\ <button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\ <button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
@ -167,14 +164,16 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"initialVolume\">Lautstärke nach dem Einschalten</label>\ <label for=\"initialVolume\">Lautstärke nach dem Einschalten</label>\
<input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"initialVolume\" name=\"initialVolume\" value=\"%INIT_VOLUME%\" required>\ <input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"initialVolume\" name=\"initialVolume\" value=\"%INIT_VOLUME%\" required>\
<label for=\"maxVolume\">Maximale Lautstärke</label>\
<input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"maxVolume\" name=\"maxVolume\" value=\"%MAX_VOLUME%\" required>\
<label for=\"maxVolumeSpeaker\">Maximale Lautstärke (Lautsprecher)</label>\
<input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"maxVolumeSpeaker\" name=\"maxVolumeSpeaker\" value=\"%MAX_VOLUME_SPEAKER%\" required>\
<label for=\"maxVolumeHeadphone\">Maximale Lautstärke (Kopfhörer)</label>\
<input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"maxVolumeHeadphone\" name=\"maxVolumeHeadphone\" value=\"%MAX_VOLUME_HEADPHONE%\" required>\
</div>\ </div>\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"initBrightness\">Neopixel-Helligkeit nach dem Einschalten</label>\ <label for=\"initBrightness\">Neopixel-Helligkeit nach dem Einschalten</label>\
<input type=\"number\" min=\"0\" max=\"255\" class=\"form-control\" id=\"initBrightness\" name=\"initBrightness\" value=\"%INIT_LED_BRIGHTBESS%\" required>\
<input type=\"number\" min=\"0\" max=\"255\" class=\"form-control\" id=\"initBrightness\" name=\"initBrightness\" value=\"%INIT_LED_BRIGHTNESS%\" required>\
<label for=\"nightBrightness\">Neopixel-Helligkeit im Nachtmodus</label>\ <label for=\"nightBrightness\">Neopixel-Helligkeit im Nachtmodus</label>\
<input type=\"number\" min=\"0\" max=\"255\" class=\"form-control\" id=\"nightBrightness\" name=\"nightBrightness\" value=\"%NIGHT_LED_BRIGHTBESS%\" required>\
<input type=\"number\" min=\"0\" max=\"255\" class=\"form-control\" id=\"nightBrightness\" name=\"nightBrightness\" value=\"%NIGHT_LED_BRIGHTNESS%\" required>\
</div>\ </div>\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"inactivityTime\">Deep-Sleep nach Inaktivität (Minuten)</label>\ <label for=\"inactivityTime\">Deep-Sleep nach Inaktivität (Minuten)</label>\
@ -267,7 +266,8 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
var myObj = {\ var myObj = {\
\"general\": {\ \"general\": {\
iVol: document.getElementById('initialVolume').value,\ iVol: document.getElementById('initialVolume').value,\
mVol: document.getElementById('maxVolume').value,\
mVolSpeaker: document.getElementById('maxVolumeSpeaker').value,\
mVolHeadphone: document.getElementById('maxVolumeHeadphone').value,\
iBright: document.getElementById('initBrightness').value,\ iBright: document.getElementById('initBrightness').value,\
nBright: document.getElementById('nightBrightness').value,\ nBright: document.getElementById('nightBrightness').value,\
iTime: document.getElementById('inactivityTime').value\ iTime: document.getElementById('inactivityTime').value\

32
src/websiteMgmt_EN.h

@ -97,10 +97,10 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
<h2>RFID-modifications</h2>\ <h2>RFID-modifications</h2>\
<form class=\"needs-validation\" action=\"#rfidModTags\" method=\"POST\" onsubmit=\"rfidMods('rfidModTags'); return false\">\ <form class=\"needs-validation\" action=\"#rfidModTags\" method=\"POST\" onsubmit=\"rfidMods('rfidModTags'); return false\">\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"rfidIdMod\">RFID-chip-ID (12-digits)</label>\
<label for=\"rfidIdMod\">RFID-chip-ID (12 digits)</label>\
<input type=\"text\" class=\"form-control\" id=\"rfidIdMod\" maxlength=\"12\" pattern=\"[0-9]{12}\" placeholder=\"%RFID_TAG_ID%\" name=\"rfidIdMod\" required>\ <input type=\"text\" class=\"form-control\" id=\"rfidIdMod\" maxlength=\"12\" pattern=\"[0-9]{12}\" placeholder=\"%RFID_TAG_ID%\" name=\"rfidIdMod\" required>\
<div class=\"invalid-feedback\">\ <div class=\"invalid-feedback\">\
Please enter a 12-digits-number.\
Please enter a number with 12 digits.\
</div>\ </div>\
<label for=\"modId\">Abspielmodus</label>\ <label for=\"modId\">Abspielmodus</label>\
<select class=\"form-control\" id=\"modId\" name=\"modId\">\ <select class=\"form-control\" id=\"modId\" name=\"modId\">\
@ -132,15 +132,12 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
</label>\ </label>\
</div>\ </div>\
<div class=\"form-group my-2 col-md-6\">\ <div class=\"form-group my-2 col-md-6\">\
<label for=\"mqttServer\">MQTT-server (IP-address)</label>\
<input type=\"text\" class=\"form-control\" id=\"mqttServer\" pattern=\"^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$\" minlength=\"7\" maxlength=\"15\" placeholder=\"z.B. 192.168.2.89\" name=\"mqttServer\" value=\"%MQTT_SERVER%\">\
<div class=\"invalid-feedback\">\
Please enter a valid IPv4-address, e.g. 192.168.2.89.\
</div>\
<label for=\"mqttServer\">MQTT-server</label>\
<input type=\"text\" class=\"form-control\" id=\"mqttServer\" minlength=\"7\" maxlength=\"%MQTT_SERVER_LENGTH%\" placeholder=\"z.B. 192.168.2.89\" name=\"mqttServer\" value=\"%MQTT_SERVER%\">\
<label for=\"mqttUser\">MQTT-username (optional):</label>\ <label for=\"mqttUser\">MQTT-username (optional):</label>\
<input type=\"text\" class=\"form-control\" id=\"mqttUser\" maxlength=\"15\" placeholder=\"Benutzername\" name=\"mqttUser\" value=\"%MQTT_USER%\">\
<input type=\"text\" class=\"form-control\" id=\"mqttUser\" maxlength=\"%MQTT_USER_LENGTH%\" placeholder=\"Benutzername\" name=\"mqttUser\" value=\"%MQTT_USER%\">\
<label for=\"mqttPwd\">Password (optional):</label>\ <label for=\"mqttPwd\">Password (optional):</label>\
<input type=\"password\" class=\"form-control\" id=\"mqttPwd\" maxlength=\"15\" placeholder=\"Passwort\" name=\"mqttPwd\" value=\"%MQTT_PWD%\">\
<input type=\"password\" class=\"form-control\" id=\"mqttPwd\" maxlength=\"%MQTT_PWD_LENGTH%\" placeholder=\"Passwort\" name=\"mqttPwd\" value=\"%MQTT_PWD%\">\
</div>\ </div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\ <button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Submit</button>\ <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\
@ -152,9 +149,9 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
<form action=\"#ftpConfig\" method=\"POST\" onsubmit=\"ftpSettings('ftpConfig'); return false\">\ <form action=\"#ftpConfig\" method=\"POST\" onsubmit=\"ftpSettings('ftpConfig'); return false\">\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"ftpUser\">FTP-username:</label>\ <label for=\"ftpUser\">FTP-username:</label>\
<input type=\"text\" class=\"form-control\" id=\"ftpUser\" maxlength=\"32\" placeholder=\"Benutzername\" name=\"ftpUser\" value=\"%FTP_USER%\" required>\
<input type=\"text\" class=\"form-control\" id=\"ftpUser\" maxlength=\"%FTP_USER_LENGTH%\" placeholder=\"Benutzername\" name=\"ftpUser\" value=\"%FTP_USER%\" required>\
<label for=\"pwd\">password:</label>\ <label for=\"pwd\">password:</label>\
<input type=\"password\" class=\"form-control\" id=\"ftpPwd\" maxlength=\"32\" placeholder=\"Passwort\" name=\"ftpPwd\" value=\"%FTP_PWD%\" required>\
<input type=\"password\" class=\"form-control\" id=\"ftpPwd\" maxlength=\"%FTP_PWD_LENGTH%\" placeholder=\"Passwort\" name=\"ftpPwd\" value=\"%FTP_PWD%\" required>\
</div>\ </div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\ <button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Submit</button>\ <button type=\"submit\" class=\"btn btn-primary\">Submit</button>\
@ -167,14 +164,16 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"initialVolume\">Volume after start</label>\ <label for=\"initialVolume\">Volume after start</label>\
<input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"initialVolume\" name=\"initialVolume\" value=\"%INIT_VOLUME%\" required>\ <input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"initialVolume\" name=\"initialVolume\" value=\"%INIT_VOLUME%\" required>\
<label for=\"maxVolume\">Maximum volume</label>\
<input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"maxVolume\" name=\"maxVolume\" value=\"%MAX_VOLUME%\" required>\
<label for=\"maxVolumeSpeaker\">Maximum volume (speaker)</label>\
<input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"maxVolumeSpeaker\" name=\"maxVolumeSpeaker\" value=\"%MAX_VOLUME_SPEAKER%\" required>\
<label for=\"maxVolumeHeadphone\">Maximum volume (headphone)</label>\
<input type=\"number\" min=\"1\" max=\"21\" class=\"form-control\" id=\"maxVolumeHeadphone\" name=\"maxVolumeHeadphone\" value=\"%MAX_VOLUME_HEADPHONE%\" required>\
</div>\ </div>\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"initBrightness\">Neopixel-brightness after start</label>\ <label for=\"initBrightness\">Neopixel-brightness after start</label>\
<input type=\"number\" min=\"0\" max=\"255\" class=\"form-control\" id=\"initBrightness\" name=\"initBrightness\" value=\"%INIT_LED_BRIGHTBESS%\" required>\
<input type=\"number\" min=\"0\" max=\"255\" class=\"form-control\" id=\"initBrightness\" name=\"initBrightness\" value=\"%INIT_LED_BRIGHTNESS%\" required>\
<label for=\"nightBrightness\">Neopixel-brightness in nightmode</label>\ <label for=\"nightBrightness\">Neopixel-brightness in nightmode</label>\
<input type=\"number\" min=\"0\" max=\"255\" class=\"form-control\" id=\"nightBrightness\" name=\"nightBrightness\" value=\"%NIGHT_LED_BRIGHTBESS%\" required>\
<input type=\"number\" min=\"0\" max=\"255\" class=\"form-control\" id=\"nightBrightness\" name=\"nightBrightness\" value=\"%NIGHT_LED_BRIGHTNESS%\" required>\
</div>\ </div>\
<div class=\"form-group col-md-6\">\ <div class=\"form-group col-md-6\">\
<label for=\"inactivityTime\">Deepsleep after inactivity (minutes)</label>\ <label for=\"inactivityTime\">Deepsleep after inactivity (minutes)</label>\
@ -267,7 +266,8 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
var myObj = {\ var myObj = {\
\"general\": {\ \"general\": {\
iVol: document.getElementById('initialVolume').value,\ iVol: document.getElementById('initialVolume').value,\
mVol: document.getElementById('maxVolume').value,\
mVolSpeaker: document.getElementById('maxVolumeSpeaker').value,\
mVolHeadphone: document.getElementById('maxVolumeHeadphone').value,\
iBright: document.getElementById('initBrightness').value,\ iBright: document.getElementById('initBrightness').value,\
nBright: document.getElementById('nightBrightness').value,\ nBright: document.getElementById('nightBrightness').value,\
iTime: document.getElementById('inactivityTime').value\ iTime: document.getElementById('inactivityTime').value\

Loading…
Cancel
Save