diff --git a/.DS_Store b/.DS_Store
index 5172429..c5e3576 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 8281e64..0f0d740 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,7 +1,7 @@
{
- // See http://go.microsoft.com/fwlink/?LinkId=827846
- // for the documentation about the extensions.json format
- "recommendations": [
- "platformio.platformio-ide"
- ]
-}
\ No newline at end of file
+ // See http://go.microsoft.com/fwlink/?LinkId=827846
+ // for the documentation about the extensions.json format
+ "recommendations": [
+ "platformio.platformio-ide"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..6e77734
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "python.pythonPath": "/Users/torsten/.platformio/penv/bin/python"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 7a8ae96..8afcced 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,7 @@ Optionally, GPIO 17 can be used to drive a NPN-transistor (BC337-40) that pulls
* 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)
* 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.
+* 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)
* compile and upload the sketch
diff --git a/html/tonuino_logo.png b/html/tonuino_logo.png
new file mode 100644
index 0000000..67231f6
Binary files /dev/null and b/html/tonuino_logo.png differ
diff --git a/html/transformHtmlToCharArray.pl b/html/transformHtmlToCharArray.pl
new file mode 100644
index 0000000..8dca048
--- /dev/null
+++ b/html/transformHtmlToCharArray.pl
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use strict;
+
+open(my $input, "<", "website.html")
+ or die "Can't open < website.html: $!";
+
+open(my $output, ">", "../src/websiteMgmt.h")
+ or die "Can't open < websiteMgmt.h: $!";
+
+print $output "static const char mgtWebsite[] PROGMEM = \"";
+while (my $row = <$input>) {
+ $row =~ s/\"/\\"/g;
+ $row =~ s/\n/\\/g;
+ print $output "$row\n";
+}
+print $output "\";";
+
+close($input);
+close($output);
\ No newline at end of file
diff --git a/html/website.html b/html/website.html
new file mode 100644
index 0000000..9d7c904
--- /dev/null
+++ b/html/website.html
@@ -0,0 +1,300 @@
+
+
+
\
+ \
+\
+";
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index ed68f0c..97ef427 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -13,6 +13,7 @@ platform = espressif32
board = nodemcu-32s
framework = arduino
monitor_speed = 115200
+board_build.partitions = no_ota.csv
lib_deps =
https://github.com/schreibfaul1/ESP32-audioI2S.git
@@ -20,6 +21,10 @@ lib_deps =
https://github.com/knolleary/pubsubclient.git
https://github.com/biologist79/ESP32FTPServer
https://github.com/FastLED/FastLED.git
- https://github.com/me-no-dev/ESPAsyncWebServer.git
https://github.com/biologist79/rfid.git
- ;https://github.com/bblanchon/ArduinoJson.git
+ ESP Async WebServer
+ https://github.com/me-no-dev/AsyncTCP
+ https://github.com/bblanchon/ArduinoJson.git
+
+extra_scripts =
+ pre:processHtml.py
\ No newline at end of file
diff --git a/processHtml.py b/processHtml.py
new file mode 100644
index 0000000..76f1db4
--- /dev/null
+++ b/processHtml.py
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+
+content = ''
+
+with open('html/website.html', 'r') as r:
+ data = r.read().replace('\n', '\\\n')
+ #content += '"'
+ #data = r.read().replace('\n', '"\n')
+ data = data.replace('\"', '\\"')
+ content += data
+
+with open('src/websiteMgmt.h', 'w') as w:
+ w.write("static const char mgtWebsite[] PROGMEM = \"")
+ w.write(content)
+ w.write("\";")
+
+r.close()
+w.close()
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index ec61615..c553532 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -24,8 +24,11 @@
#include
#endif
#include "logmessages.h"
+#include "websiteMgmt.h"
+#include
#include
#include "website.h"
+#include
@@ -162,7 +165,7 @@ uint8_t const cardIdSize = 4; // RFID
// Volume
uint8_t maxVolume = 21; // Maximum volume that can be adjusted
uint8_t minVolume = 0; // Lowest volume that can be adjusted
-uint8_t initVolume = 3; // 0...21
+uint8_t initVolume = 3; // 0...21 (If not found in NVS, this one will be taken)
// Sleep
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)
@@ -213,7 +216,7 @@ char ftpUser[10] = "esp32"; // FTP-user
char ftpPassword[15] = "esp32"; // FTP-password
// MQTT-configuration
-char mqtt_server[16] = "192.168.2.43"; // IP-address of MQTT-server
+char mqtt_server[16] = "192.168.2.43"; // IP-address of MQTT-server (if not found in NVS this one will be taken)
#ifdef MQTT_ENABLE
#define DEVICE_HOSTNAME "ESP32-Tonuino" // Name that that is used for MQTT
static const char topicSleepCmnd[] PROGMEM = "Cmnd/Tonuino/Sleep";
@@ -245,6 +248,8 @@ void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
AsyncWebServer wServer(81);
+AsyncWebSocket ws("/ws");
+AsyncEventSource events("/events");
// Audio/mp3
SPIClass spiSD(HSPI);
@@ -330,6 +335,7 @@ void sortPlaylist(const char** arr, int n);
bool startsWith(const char *str, const char *pre);
void trackControlToQueueSender(const uint8_t trackCommand);
void rfidPreferenceLookupHandler (void);
+void sendWebsocketData(uint32_t client, uint8_t code);
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 volumeToQueueSender(const int32_t _newVolume);
@@ -2312,6 +2318,7 @@ void rfidPreferenceLookupHandler (void) {
lastTimeActiveTimestamp = millis();
snprintf(logBuf, sizeof(logBuf)/sizeof(logBuf[0]), "%s: %s", (char *) FPSTR(rfidTagReceived), rfidTagId);
currentRfidTagId = strdup(rfidTagId);
+ sendWebsocketData(0, 10); // Push new rfidTagId to all websocket-clients
loggerNl(logBuf, LOGLEVEL_INFO);
String s = prefsRfid.getString(rfidTagId, "-1"); // Try to lookup rfidId in NVS
@@ -2443,12 +2450,13 @@ wl_status_t wifiManager(void) {
WiFi.begin(_ssid, _pwd);
uint8_t tryCount=0;
- while (WiFi.status() != WL_CONNECTED && tryCount <= 4) {
+ while (WiFi.status() != WL_CONNECTED && tryCount <= 6) {
delay(500);
Serial.print(F("."));
+ Serial.print(WiFi.status());
tryCount++;
wifiCheckLastTimestamp = millis();
- if (tryCount >= 4 && WiFi.status() == WL_CONNECT_FAILED) {
+ if (tryCount >= 2 && WiFi.status() == WL_CONNECT_FAILED) {
WiFi.begin(_ssid, _pwd); // ESP32-workaround
}
}
@@ -2469,6 +2477,236 @@ wl_status_t wifiManager(void) {
}
+// Used for substitution of some variables/templates of html-file
+String templateProcessor(const String& templ) {
+ if(templ == "FTP_USER") {
+ return prefsSettings.getString("ftpuser", "-1");
+ } else if (templ == "FTP_PWD") {
+ return prefsSettings.getString("ftppassword", "-1");
+ } else if (templ == "INIT_LED_BRIGHTBESS") {
+ return String(prefsSettings.getUChar("iLedBrightness", 0));
+ } else if (templ == "NIGHT_LED_BRIGHTBESS") {
+ return String(prefsSettings.getUChar("nLedBrightness", 0));
+ } else if (templ == "MAX_INACTIVITY") {
+ return String(prefsSettings.getUInt("mInactiviyT", 0));
+ } else if (templ == "INIT_VOLUME") {
+ return String(prefsSettings.getUInt("initVolume", 0));
+ } else if (templ == "MAX_VOLUME") {
+ return String(prefsSettings.getUInt("maxVolume", 0));
+ } else if (templ == "MQTT_SERVER") {
+ return prefsSettings.getString("mqttServer", "-1");
+ } else if (templ == "MQTT_ENABLE") {
+ if (enableMqtt) {
+ return String("checked=\"checked\"");
+ } else {
+ return String();
+ }
+ } else if (templ == "IPv4") {
+ myIP = WiFi.localIP();
+ snprintf(logBuf, sizeof(logBuf) / sizeof(logBuf[0]), "%d.%d.%d.%d", myIP[0], myIP[1], myIP[2], myIP[3]);
+ return String(logBuf);
+ } else if (templ == "RFID_TAG_ID") {
+ return String(currentRfidTagId);
+ }
+
+ return String();
+}
+
+
+// Takes inputs from webgui, parses JSON and saves values in NVS
+// If operation was successful (NVS-write is verified) true is returned
+bool processJsonRequest(char *_serialJson) {
+ StaticJsonDocument<1000> doc;
+ DeserializationError error = deserializeJson(doc, _serialJson);
+ JsonObject object = doc.as();
+
+ if (error) {
+ Serial.print(F("deserializeJson() failed: "));
+ Serial.println(error.c_str());
+ return false;
+ }
+
+ if (doc.containsKey("general")) {
+ uint8_t iVol = doc["general"]["iVol"].as();
+ uint8_t mVol = doc["general"]["mVol"].as();
+ uint8_t iBright = doc["general"]["iBright"].as();
+ uint8_t nBright = doc["general"]["nBright"].as();
+ uint8_t iTime = doc["general"]["iTime"].as();
+
+ prefsSettings.putUInt("initVolume", iVol);
+ prefsSettings.putUInt("maxVolume", mVol);
+ prefsSettings.putUChar("iLedBrightness", iBright);
+ prefsSettings.putUChar("nLedBrightness", nBright);
+ prefsSettings.putUInt("mInactiviyT", iTime);
+
+ // Check if settings were written successfully
+ if (prefsSettings.getUInt("initVolume", 0) != iVol ||
+ prefsSettings.getUInt("maxVolume", 0) != mVol ||
+ prefsSettings.getUChar("iLedBrightness", 0) != iBright ||
+ prefsSettings.getUChar("nLedBrightness", 0) != nBright ||
+ prefsSettings.getUInt("mInactiviyT", 0) != iTime) {
+ Serial.println("net gut!");
+ return false;
+ }
+
+ } else if (doc.containsKey("ftp")) {
+ const char *_ftpUser = doc["ftp"]["ftpUser"];
+ const char *_ftpPwd = doc["ftp"]["ftpPwd"];
+
+ prefsSettings.putString("ftpuser", (String) _ftpUser);
+ prefsSettings.putString("ftppassword", (String) _ftpPwd);
+
+ if(!(String(_ftpUser).equals(prefsSettings.getString("ftpuser", "-1")) ||
+ String(_ftpPwd).equals(prefsSettings.getString("ftppassword", "-1")))) {
+ Serial.println("net gut2!");
+ return false;
+ }
+
+ } else if (doc.containsKey("mqtt")) {
+ uint8_t _mqttEnable = doc["mqtt"]["mqttEnable"].as();
+ const char *_mqttServer = object["mqtt"]["mqttServer"];
+ prefsSettings.putUChar("enableMQTT", _mqttEnable);
+ prefsSettings.putString("mqttServer", (String) _mqttServer);
+
+ if ((prefsSettings.getUChar("enableMQTT", 99) != _mqttEnable) ||
+ (!String(_mqttServer).equals(prefsSettings.getString("mqttServer", "-1")))) {
+ return false;
+ }
+
+ } else if (doc.containsKey("rfidMod")) {
+ const char *_rfidIdModId = object["rfidMod"]["rfidIdMod"];
+ uint8_t _modId = object["rfidMod"]["modId"];
+ char rfidString[12];
+ snprintf(rfidString, sizeof(rfidString) / sizeof(rfidString[0]), "%s0%s0%s%u%s0", stringDelimiter, stringDelimiter, stringDelimiter, _modId, stringDelimiter);
+ prefsRfid.putString(_rfidIdModId, rfidString);
+
+ String s = prefsRfid.getString(_rfidIdModId, "-1");
+ if (s.compareTo(rfidString)) {
+ return false;
+ }
+
+ } else if (doc.containsKey("rfidAssign")) {
+ const char *_rfidIdModId = object["rfidAssign"]["rfidIdMusic"];
+ const char *_fileOrUrl = object["rfidAssign"]["fileOrUrl"];
+ uint8_t _playMode = object["rfidAssign"]["playMode"];
+ char rfidString[275];
+ snprintf(rfidString, sizeof(rfidString) / sizeof(rfidString[0]), "%s%s%s0%s%u%s0", stringDelimiter, _fileOrUrl, stringDelimiter, stringDelimiter, _playMode, stringDelimiter);
+ prefsRfid.putString(_rfidIdModId, rfidString);
+
+ String s = prefsRfid.getString(_rfidIdModId, "-1");
+ if (s.compareTo(rfidString)) {
+ return false;
+ }
+
+ } else if (doc.containsKey("wifiConfig")) {
+ const char *_ssid = object["wifiConfig"]["ssid"];
+ const char *_pwd = object["wifiConfig"]["pwd"];
+
+ prefsSettings.putString("SSID", _ssid);
+ prefsSettings.putString("Password", _pwd);
+
+ String sSsid = prefsSettings.getString("SSID", "-1");
+ String sPwd = prefsSettings.getString("Password", "-1");
+
+ if (sSsid.compareTo(_ssid) || sPwd.compareTo(_pwd)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+// Sends JSON-answers via websocket
+void sendWebsocketData(uint32_t client, uint8_t code) {
+ const size_t CAPACITY = JSON_OBJECT_SIZE(1) + 20;
+ StaticJsonDocument doc;
+ JsonObject object = doc.to();
+
+ if (code == 1) {
+ object["status"] = "ok";
+ } else if (code == 2) {
+ object["status"] = "error";
+ } else if (code == 10) {
+ object["rfidId"] = currentRfidTagId;
+ }
+ char jBuf[50];
+ serializeJson(doc, jBuf, sizeof(jBuf) / sizeof(jBuf[0]));
+
+ if (client == 0) {
+ ws.printfAll(jBuf);
+ } else {
+ ws.printf(client, jBuf);
+ }
+}
+
+
+// Processes websocket-requests
+void onWebsocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
+ if (type == WS_EVT_CONNECT){
+ //client connected
+ Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
+ client->printf("Hello Client %u :)", client->id());
+ client->ping();
+ } else if (type == WS_EVT_DISCONNECT) {
+ //client disconnected
+ Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id());
+ } else if (type == WS_EVT_ERROR) {
+ //error was received from the other end
+ Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data);
+ } else if (type == WS_EVT_PONG) {
+ //pong message was received (in response to a ping request maybe)
+ Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:"");
+ } else if (type == WS_EVT_DATA) {
+ //data packet
+ AwsFrameInfo * info = (AwsFrameInfo*)arg;
+ if (info->final && info->index == 0 && info->len == len) {
+ //the whole message is in a single frame and we got all of it's data
+ Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len);
+ uint8_t returnCode;
+
+ if (processJsonRequest((char*)data)) {
+ returnCode = 1;
+ } else {
+ returnCode = 0;
+ }
+ sendWebsocketData(client->id(), 1);
+ //ws.printf(client->id(), doc);
+
+ if (info->opcode == WS_TEXT) {
+ data[len] = 0;
+ Serial.printf("%s\n", (char*)data);
+ } else {
+ for(size_t i=0; i < info->len; i++){
+ Serial.printf("%02x ", data[i]);
+ }
+ Serial.printf("\n");
+ }
+
+ if (info->opcode == WS_TEXT) {
+ //client->text("I got your text message");
+ } else {
+ //client->binary("I got your binary message");
+ }
+ } else {
+ if (info->message_opcode == WS_TEXT) {
+ data[len] = 0;
+ }
+
+ if ((info->index + len) == info->len) {
+ if (info->final) {
+ if (info->message_opcode == WS_TEXT) {
+ //client->text("I got your text message");
+ } else {
+ //client->binary("I got your binary message");
+ }
+ }
+ }
+ }
+ }
+}
+
+
void setup() {
Serial.begin(115200);
srand(esp_random());
@@ -2556,6 +2794,7 @@ void setup() {
uint8_t nvsILedBrightness = prefsSettings.getUChar("iLedBrightness", 0);
if (nvsILedBrightness) {
initialLedBrightness = nvsILedBrightness;
+ ledBrightness = nvsILedBrightness;
snprintf(logBuf, sizeof(logBuf) / sizeof(logBuf[0]), "%s: %d", (char *) FPSTR(initialBrightnessfromNvs), nvsILedBrightness);
loggerNl(logBuf, LOGLEVEL_INFO);
} else {
@@ -2633,11 +2872,12 @@ void setup() {
uint8_t nvsEnableMqtt = prefsSettings.getUChar("enableMQTT", 99);
switch (nvsEnableMqtt) {
case 99:
- prefsSettings.putUChar("enableMQTT", 0);
+ prefsSettings.putUChar("enableMQTT", enableMqtt);
loggerNl((char *) FPSTR(wroteMqttFlagToNvs), LOGLEVEL_ERROR);
break;
case 1:
- prefsSettings.putUChar("enableMQTT", enableMqtt);
+ //prefsSettings.putUChar("enableMQTT", enableMqtt);
+ enableMqtt = nvsEnableMqtt;
snprintf(logBuf, sizeof(logBuf) / sizeof(logBuf[0]), "%s: %u", (char *) FPSTR(loadedMqttActiveFromNvs), nvsEnableMqtt);
loggerNl(logBuf, LOGLEVEL_INFO);
break;
@@ -2725,8 +2965,12 @@ void setup() {
lastTimeActiveTimestamp = millis(); // initial set after boot
if (wifiManager() == WL_CONNECTED) {
+ // Websocket
+ ws.onEvent(onWebsocketEvent);
+ wServer.addHandler(&ws);
+
wServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
- request->send(200, "text/plain", "Hello, world");
+ request->send_P(200, "text/html", mgtWebsite, templateProcessor);
});
// Send a GET request to /get?message=
diff --git a/src/websiteMgmt.h b/src/websiteMgmt.h
new file mode 100644
index 0000000..a1fff48
--- /dev/null
+++ b/src/websiteMgmt.h
@@ -0,0 +1,301 @@
+static const char mgtWebsite[] PROGMEM = "\
+\
+ \
+ ESPuino-Konfiguration\
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+
\
+
WLAN-Konfiguration
\
+ \
+ \
+
\
+
\
+
RFID-Zuweisungen
\
+ \
+ \
+
\
+
\
+
RFID-Modifkationen
\
+ \
+ \
+
\
+
\
+
MQTT-Konfiguration
\
+ \
+ \
+
\
+
\
+
FTP-Konfiguration
\
+ \
+ \
+
\
+
\
+
Allgemeine Konfiguration
\
+ \
+ \
+
\
+ \
+ \
+\
+";
\ No newline at end of file
diff --git a/svg/README.md b/svg/README.md
index 494620d..1076dd8 100644
--- a/svg/README.md
+++ b/svg/README.md
@@ -1,2 +1,2 @@
-holes_n_ring.svg: Used to CNC a ring and the holes into a wood-enclosure
-inner ring.svg: Used to cnc a ring that the speaker is screwed to. The ring is glued to the enclosure.
\ No newline at end of file
+`holes_n_ring.svg`: Used to CNC a ring and the holes into a wood-enclosure
+`inner ring.svg`: Used to cnc a ring that the speaker is screwed to. The ring is glued to the enclosure.
\ No newline at end of file