Browse Source

Webgui-support added

master
Torsten Stauder 5 years ago
parent
commit
c5fec789c9
  1. BIN
      .DS_Store
  2. 12
      .vscode/extensions.json
  3. 3
      .vscode/settings.json
  4. 2
      README.md
  5. BIN
      html/tonuino_logo.png
  6. 20
      html/transformHtmlToCharArray.pl
  7. 300
      html/website.html
  8. 263
      html/websiteMgmt.h
  9. 9
      platformio.ini
  10. 18
      processHtml.py
  11. 258
      src/main.cpp
  12. 301
      src/websiteMgmt.h
  13. 4
      svg/README.md

BIN
.DS_Store

12
.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"
]
}
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

3
.vscode/settings.json

@ -0,0 +1,3 @@
{
"python.pythonPath": "/Users/torsten/.platformio/penv/bin/python"
}

2
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

BIN
html/tonuino_logo.png

After

Width: 379  |  Height: 426  |  Size: 109 KiB

20
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);

300
html/website.html

@ -0,0 +1,300 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>ESPuino-Konfiguration</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://ts-cs.de/css/bootstrap.min.css">
<script src="https://ts-cs.de/js/jquery.min.js"></script>
<script src="https://ts-cs.de/js/popper.min.js"></script>
<script src="https://ts-cs.de/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-sm bg-primary navbar-dark">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand">
<img src="./tonuino_logo.png" width="30" height="30" class="d-inline-block align-top" alt="" />
Tonuino
</a>
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="#wifiConfig">WLAN</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#rfidMusicTags">RFID-Zuweisungen</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#rfidModTags">RFID-Modifikationen</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#mqttConfig">MQTT</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#ftpConfig">FTP</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#generalConfig">Allgemein</a>
</li>
</ul>
</div>
</nav>
<br />
<div class="container" id="wifiConfig">
<h2>WLAN-Konfiguration</h2>
<form action="#wifiConfig" method="POST" onsubmit="wifiConfig('wifiConfig'); return false">
<div class="form-group col-md-6">
<label for="ssid">WLAN-Name (SSID):</label>
<input type="text" class="form-control" id="ssid" placeholder="SSID" name="ssid" required>
<div class="invalid-feedback">
Bitte SSID des WLANs eintragen.
</div>
<label for="pwd">Passwort:</label>
<input type="password" class="form-control" id="pwd" placeholder="Passwort" name="pwd" required>
</div>
<button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div class="messages col-md-6 my-2"></div>
</div>
<div class="container my-5" id="rfidMusicTags">
<h2>RFID-Zuweisungen</h2>
<form action="#rfidMusicTags" method="POST" onsubmit="rfidAssign('rfidMusicTags'); return false">
<div class="form-group col-md-6">
<label for="rfidIdMusic">RFID-Chip-Nummer</label>
<input type="text" class="form-control" id="rfidIdMusic" maxlength="12" pattern="[0-9]{12}" placeholder="%RFID_TAG_ID%" name="rfidIdMusic" required>
<label for="fileOrUrl">Datei, Verzeichnis oder URL (URL nur für Webradio)</label>
<input type="text" class="form-control" id="fileOrUrl" maxlength="255" placeholder="z.B. /mp3/Hoerspiele/Yakari/Yakari_und_seine_Freunde.mp3" name="fileOrUrl" required>
<label for="playMode">Abspielmodus</label>
<select class="form-control" id="playMode" name="playMode">
<option value="1">Einzelner Titel</option>
<option value="2">Einzelner Titel (Endlosschleife)</option>
<option value="3">Hörbuch</option>
<option value="4">Hörbuch (Endlosschleife)</option>
<option value="5">Alle Titel eines Verzeichnis (sortiert)</option>
<option value="6">Alle Titel eines Verzeichnis (zufällig)</option>
<option value="7">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option>
<option value="8">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>
<option value="9">Webradio</option>
</select>
</div>
<button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div class="messages col-md-6 my-2"></div>
</div>
<div class="container my-5" id="rfidModTags">
<h2>RFID-Modifkationen</h2>
<form class="needs-validation" action="#rfidModTags" method="POST" onsubmit="rfidMods('rfidModTags'); return false" novalidate>
<div class="form-group col-md-6">
<label for="rfidIdMod">RFID-Chip-Nummer</label>
<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">
Bitte eine 12-stellige Zahl eingeben.
</div>
<label for="modId">Abspielmodus</label>
<select class="form-control" id="modId" name="modId">
<option value="100">Tastensperre</option>
<option value="101">Schlafen nach 15 Minuten</option>
<option value="102">Schlafen nach 30 Minuten</option>
<option value="103">Schlafen nach 1 Stunde</option>
<option value="104">Schlafen nach 2 Stunden</option>
<option value="105">Schlafen nach Ende des Titels</option>
<option value="106">Schlafen nach Ende der Playlist</option>
<option value="110">Wiederhole Playlist (endlos)</option>
<option value="111">Wiederhole Titel (endlos)</option>
<option value="112">Dimme LEDs (Nachtmodus)</option>
</select>
</div>
<button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div class="messages col-md-6 my-2"></div>
</div>
<div class="container my-5" id="mqttConfig">
<h2>MQTT-Konfiguration</h2>
<form class="needs-validation" action="#mqttConfig" method="POST" onsubmit="mqttSettings('mqttConfig'); return false" novalidate>
<div class="form-check col-md-6">
<input class="form-check-input" type="checkbox" value="1" id="mqttEnable" name="mqttEnable" %MQTT_ENABLE%>
<label class="form-check-label" for="mqttEnable">
MQTT aktivieren
</label>
</div>
<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,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$" 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>
</div>
<button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div class="messages col-md-6 my-2"></div>
</div>
<div class="container" id="ftpConfig">
<h2>FTP-Konfiguration</h2>
<form action="#ftpConfig" method="POST" onsubmit="ftpSettings('ftpConfig'); return false">
<div class="form-group col-md-6">
<label for="ftpUser">FTP-Benutzername:</label>
<input type="text" class="form-control" id="ftpUser" maxlength="32" placeholder="Benutzername" name="ftpUser" value="%FTP_USER%" required>
<label for="pwd">Passwort:</label>
<input type="password" class="form-control" id="ftpPwd" maxlength="32" placeholder="Passwort" name="ftpPwd" value="%FTP_PWD%" required>
</div>
<button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div class="messages col-md-6 my-2"></div>
</div>
<div class="container my-5" id="generalConfig">
<h2>Allgemeine Konfiguration</h2>
<form action="#generalConfig" method="POST" onsubmit="genSettings('generalConfig'); return false">
<div class="form-group col-md-6">
<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>
<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>
</div>
<div class="form-group col-md-6">
<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>
<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>
</div>
<div class="form-group col-md-6">
<label for="inactivityTime">Deep-Sleep nach Inaktivität (Minuten)</label>
<input type="number" min="1" max="1440" class="form-control" id="inactivityTime" name="inactivityTime" value="%MAX_INACTIVITY%" required>
</div>
<button type="reset" class="btn btn-secondary">Reset</button>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div class="messages col-md-6 my-2"></div>
</div>
<script>
var lastIdclicked = '';
var errorBox = '<div class="alert alert-danger alert-dismissible fade show" role="alert">Es ist ein Fehler aufgetreten!<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>';
var okBox = '<div class="alert alert-success alert-dismissible fade show" role="alert">Aktion erfolgreich ausgeführt.<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>';
(function() {
'use strict';
window.addEventListener('load', function() {
/* Fetch all the forms we want to apply custom Bootstrap validation styles to*/
var forms = document.getElementsByClassName('needs-validation');
/* Loop over them and prevent submission*/
var validation = Array.prototype.filter.call(forms, function(form) {
form.addEventListener('submit', function(event) {
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add('was-validated');
}, false);
});
}, false);
});
let socket = new WebSocket("ws://%IPv4%:81/ws");
socket.onmessage = function(event) {
console.log(event.data);
var socketMsg = JSON.parse(event.data);
if (socketMsg.rfidId != null) {
document.getElementById('rfidIdMod').value = socketMsg.rfidId;
document.getElementById('rfidIdMusic').value = socketMsg.rfidId;
$("#rfidIdMusic").fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500);
$("#rfidIdMod").fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500);
} if (socketMsg.status != null) {
if (socketMsg.status == 'ok') {
$("#" + lastIdclicked).find('.messages').html(okBox);
} else {
$("#" + lastIdclicked).find('.messages').html(errorBox);
}
}
};
function genSettings(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"general": {
iVol: document.getElementById('initialVolume').value,
mVol: document.getElementById('maxVolume').value,
iBright: document.getElementById('initBrightness').value,
nBright: document.getElementById('nightBrightness').value,
iTime: document.getElementById('inactivityTime').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function ftpSettings(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"ftp": {
ftpUser: document.getElementById('ftpUser').value,
ftpPwd: document.getElementById('ftpPwd').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function mqttSettings(clickedId) {
lastIdclicked = clickedId;
var val;
if (document.getElementById('mqttEnable').checked) {
val = document.getElementById('mqttEnable').value;
} else {
val = 0;
}
var myObj = {
"mqtt": {
mqttEnable: val,
mqttServer: document.getElementById('mqttServer').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function rfidMods(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"rfidMod": {
rfidIdMod: document.getElementById('rfidIdMod').value,
modId: document.getElementById('modId').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function rfidAssign(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"rfidAssign": {
rfidIdMusic: document.getElementById('rfidIdMusic').value,
fileOrUrl: document.getElementById('fileOrUrl').value,
playMode: document.getElementById('playMode').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function wifiConfig(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"wifiConfig": {
ssid: document.getElementById('ssid').value,
pwd: document.getElementById('pwd').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
</script>
</body>
</html>

263
html/websiteMgmt.h

@ -0,0 +1,263 @@
static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
<html lang=\"de\">\
<head>\
<title>ESPuino-Konfiguration</title>\
<meta charset=\"utf-8\">\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
<link rel=\"stylesheet\" href=\"https://ts-cs.de/css/bootstrap.min.css\">\
<script src=\"https://ts-cs.de/js/jquery.min.js\"></script>\
<script src=\"https://ts-cs.de/js/popper.min.js\"></script>\
<script src=\"https://ts-cs.de/js/bootstrap.min.js\"></script>\
</head>\
<body>\
<nav class=\"navbar navbar-expand-sm bg-primary navbar-dark\">\
<button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\
<span class=\"navbar-toggler-icon\"></span>\
</button>\
<a class=\"navbar-brand\">\
<img src=\"./tonuino_logo.png\" width=\"30\" height=\"30\" class=\"d-inline-block align-top\" alt=\"\" />\
Tonuino\
</a>\
<div class=\"collapse navbar-collapse\" id=\"collapsibleNavbar\">\
<ul class=\"navbar-nav mr-auto\">\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#wifiConfig\">WLAN</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#rfidMusicTags\">RFID-Zuweisungen</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#rfidModTags\">RFID-Modifikationen</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#mqttConfig\">MQTT</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#ftpConfig\">FTP</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#generalConfig\">Allgemein</a>\
</li>\
</ul>\
</div>\
</nav>\
<br />\
<div class=\"container\" id=\"wifiConfig\">\
<h2>WLAN-Konfiguration</h2>\
<form action=\"#wifiConfig\" method=\"GET\">\
<div class=\"form-group col-md-6\">\
<label for=\"SSID\">WLAN-Name (SSID):</label>\
<input type=\"text\" class=\"form-control\" id=\"SSID\" placeholder=\"SSID\" name=\"SSID\" required>\
<div class=\"invalid-feedback\">\
Bitte SSID des WLANs eintragen.\
</div>\
<label for=\"pwd\">Passwort:</label>\
<input type=\"password\" class=\"form-control\" id=\"pwd\" placeholder=\"Passwort\" name=\"pwd\" required>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\" onclick=\"wifiConfig()\">Absenden</button>\
</form>\
</div>\
<div class=\"container my-5\" id=\"rfidMusicTags\">\
<h2>RFID-Zuweisungen</h2>\
<form action=\"#rfidMusicTags\" method=\"GET\">\
<div class=\"form-group col-md-6\">\
<label for=\"rfidIdMusic\">RFID-Chip-Nummer</label>\
<input type=\"text\" class=\"form-control\" id=\"rfidIdMusic\" maxlength=\"12\" pattern=\"[0-9]{12}\" placeholder=\"012345678912\" name=\"rfidIdMusic\" required>\
<label for=\"fileOrUrl\">Datei, Verzeichnis oder URL (URL nur für Webradio)</label>\
<input type=\"text\" class=\"form-control\" id=\"fileOrUrl\" maxlength=\"255\" placeholder=\"z.B. /mp3/Hoerspiele/Yakari/Yakari_und_seine_Freunde.mp3\" name=\"fileOrUrl\" required>\
<label for=\"playMode\">Abspielmodus</label>\
<select class=\"form-control\" id=\"playMode\" name=\"playMode\">\
<option value=\"1\">Einzelner Titel</option>\
<option value=\"2\">Einzelner Titel (Endlosschleife)</option>\
<option value=\"3\">Hörbuch</option>\
<option value=\"4\">Hörbuch (Endlosschleife)</option>\
<option value=\"5\">Alle Titel eines Verzeichnis (sortiert)</option>\
<option value=\"6\">Alle Titel eines Verzeichnis (zufällig)</option>\
<option value=\"7\">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option>\
<option value=\"8\">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>\
<option value=\"9\">Webradio</option>\
</select>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\" onclick=\"rfidAssign()\">Absenden</button>\
</form>\
</div>\
<div class=\"container my-5\" id=\"rfidModTags\">\
<h2>RFID-Modifkationen</h2>\
<form class=\"needs-validation\" action=\"#rfidModTags\" method=\"GET\" novalidate>\
<div class=\"form-group col-md-6\">\
<label for=\"rfidIdMod\">RFID-Chip-Nummer</label>\
<input type=\"text\" class=\"form-control\" id=\"rfidIdMod\" maxlength=\"12\" pattern=\"[0-9]{12}\" placeholder=\"012345678912\" name=\"rfidIdMod\" required>\
<div class=\"invalid-feedback\">\
Bitte eine 12-stellige Zahl eingeben.\
</div>\
<label for=\"modId\">Abspielmodus</label>\
<select class=\"form-control\" id=\"modId\" name=\"modId\">\
<option value=\"100\">Tastensperre</option>\
<option value=\"101\">Schlafen nach 15 Minuten</option>\
<option value=\"102\">Schlafen nach 30 Minuten</option>\
<option value=\"103\">Schlafen nach 1 Stunde</option>\
<option value=\"104\">Schlafen nach 2 Stunden</option>\
<option value=\"105\">Schlafen nach Ende des Titels</option>\
<option value=\"106\">Schlafen nach Ende der Playlist</option>\
<option value=\"110\">Wiederhole Playlist (endlos)</option>\
<option value=\"111\">Wiederhole Titel (endlos)</option>\
<option value=\"112\">Dimme LEDs (Nachtmodus)</option>\
</select>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\" onclick=\"rfidMods()\">Absenden</button>\
</form>\
</div>\
<div class=\"container my-5\" id=\"mqttConfig\">\
<h2>MQTT-Konfiguration</h2>\
<form class=\"needs-validation\" action=\"#mqttConfig\" method=\"GET\" novalidate>\
<div class=\"form-check col-md-6\">\
<input class=\"form-check-input\" type=\"checkbox\" value=\"1\" id=\"mqttEnable\" name=\"mqttEnable\" %MQTT_ENABLE%>\
<label class=\"form-check-label\" for=\"mqttEnable\">\
MQTT aktivieren\
</label>\
</div>\
<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,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$\" 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>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\" onclick=\"mqttSettings()\">Absenden</button>\
</form>\
</div>\
<div class=\"container\" id=\"ftpConfig\">\
<h2>FTP-Konfiguration</h2>\
<form action=\"#ftpConfig\" method=\"GET\">\
<div class=\"form-group col-md-6\">\
<label for=\"ftpUser\">FTP-Benutzername:</label>\
<input type=\"text\" class=\"form-control\" id=\"ftpUser\" maxlength=\"32\" placeholder=\"Benutzername\" name=\"ftpUser\" value=\"%FTP_USER%\" required>\
<label for=\"pwd\">Passwort:</label>\
<input type=\"password\" class=\"form-control\" id=\"ftpPwd\" maxlength=\"32\" placeholder=\"Passwort\" name=\"ftpPwd\" value=\"%FTP_PWD%\" required>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\" onClick=\"ftpSettings()\">Absenden</button>\
</form>\
</div>\
<div class=\"container my-5\" id=\"generalConfig\">\
<h2>Allgemeine Konfiguration</h2>\
<form action=\"#generalConfig\" method=\"GET\">\
<div class=\"form-group col-md-6\">\
<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>\
<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>\
</div>\
<div class=\"form-group col-md-6\">\
<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>\
<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>\
</div>\
<div class=\"form-group col-md-6\">\
<label for=\"inactivityTime\">Deep-Sleep nach Inaktivität (Minuten)</label>\
<input type=\"number\" min=\"1\" max=\"1440\" class=\"form-control\" id=\"inactivityTime\" name=\"inactivityTime\" value=\"%MAX_INACTIVITY%\" required>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\" onClick=\"genSettings()\">Absenden</button>\
</form>\
<script>\
(function() {\
'use strict';\
window.addEventListener('load', function() {\
// Fetch all the forms we want to apply custom Bootstrap validation styles to\
var forms = document.getElementsByClassName('needs-validation');\
// Loop over them and prevent submission\
var validation = Array.prototype.filter.call(forms, function(form) {\
form.addEventListener('submit', function(event) {\
if (form.checkValidity() === false) {\
event.preventDefault();\
event.stopPropagation();\
}\
form.classList.add('was-validated');\
}, false);\
});\
}, false);\
});\
\
let socket = new WebSocket(\"ws://%IPv4%:81/ws\");\
\
function genSettings() {\
\
var myObj = {\
\"general\": {\
iVol: document.getElementById('initialVolume').value,\
mVol: document.getElementById('maxVolume').value,\
iBright: document.getElementById('initBrightness').value,\
nBright: document.getElementById('nightBrightness').value,\
iTime: document.getElementById('inactivityTime').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function ftpSettings() {\
var myObj = {\
\"ftp\": {\
ftpUser: document.getElementById('ftpUser').value,\
ftpPwd: document.getElementById('ftpPwd').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function mqttSettings() {\
var myObj = {\
\"mqtt\": {\
enable: document.getElementById('mqttEnable').value,\
server: document.getElementById('mqttServer').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function rfidMods() {\
var myObj = {\
\"rfidMod\": {\
rfidId: document.getElementById('rfidIdMod').value,\
modId: document.getElementById('modId').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function rfidAssign() {\
var myObj = {\
\"rfidAssign\": {\
rfidId: document.getElementById('rfidIdMusic').value,\
fileOrUrl: document.getElementById('fileOrUrl').value,\
playmode: document.getElementById('playMode').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function wifiConfig() {\
var myObj = {\
\"wifiConfig\": {\
ssid: document.getElementById('SSID').value,\
pwd: document.getElementById('pwd').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
</script>\
</div>\
</body>\
</html>\
";

9
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

18
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()

258
src/main.cpp

@ -24,8 +24,11 @@
#include <FastLED.h>
#endif
#include "logmessages.h"
#include "websiteMgmt.h"
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "website.h"
#include <ArduinoJson.h>
@ -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<JsonObject>();
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>();
uint8_t mVol = doc["general"]["mVol"].as<uint8_t>();
uint8_t iBright = doc["general"]["iBright"].as<uint8_t>();
uint8_t nBright = doc["general"]["nBright"].as<uint8_t>();
uint8_t iTime = doc["general"]["iTime"].as<uint8_t>();
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<uint8_t>();
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<CAPACITY> doc;
JsonObject object = doc.to<JsonObject>();
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 <IP>/get?message=<message>

301
src/websiteMgmt.h

@ -0,0 +1,301 @@
static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
<html lang=\"de\">\
<head>\
<title>ESPuino-Konfiguration</title>\
<meta charset=\"utf-8\">\
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
<link rel=\"stylesheet\" href=\"https://ts-cs.de/css/bootstrap.min.css\">\
<script src=\"https://ts-cs.de/js/jquery.min.js\"></script>\
<script src=\"https://ts-cs.de/js/popper.min.js\"></script>\
<script src=\"https://ts-cs.de/js/bootstrap.min.js\"></script>\
</head>\
<body>\
<nav class=\"navbar navbar-expand-sm bg-primary navbar-dark\">\
<button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\
<span class=\"navbar-toggler-icon\"></span>\
</button>\
<a class=\"navbar-brand\">\
<img src=\"./tonuino_logo.png\" width=\"30\" height=\"30\" class=\"d-inline-block align-top\" alt=\"\" />\
Tonuino\
</a>\
<div class=\"collapse navbar-collapse\" id=\"collapsibleNavbar\">\
<ul class=\"navbar-nav mr-auto\">\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#wifiConfig\">WLAN</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#rfidMusicTags\">RFID-Zuweisungen</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#rfidModTags\">RFID-Modifikationen</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#mqttConfig\">MQTT</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#ftpConfig\">FTP</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#generalConfig\">Allgemein</a>\
</li>\
</ul>\
</div>\
</nav>\
<br />\
<div class=\"container\" id=\"wifiConfig\">\
<h2>WLAN-Konfiguration</h2>\
<form action=\"#wifiConfig\" method=\"POST\" onsubmit=\"wifiConfig('wifiConfig'); return false\">\
<div class=\"form-group col-md-6\">\
<label for=\"ssid\">WLAN-Name (SSID):</label>\
<input type=\"text\" class=\"form-control\" id=\"ssid\" placeholder=\"SSID\" name=\"ssid\" required>\
<div class=\"invalid-feedback\">\
Bitte SSID des WLANs eintragen.\
</div>\
<label for=\"pwd\">Passwort:</label>\
<input type=\"password\" class=\"form-control\" id=\"pwd\" placeholder=\"Passwort\" name=\"pwd\" required>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
</form>\
<div class=\"messages col-md-6 my-2\"></div>\
</div>\
<div class=\"container my-5\" id=\"rfidMusicTags\">\
<h2>RFID-Zuweisungen</h2>\
<form action=\"#rfidMusicTags\" method=\"POST\" onsubmit=\"rfidAssign('rfidMusicTags'); return false\">\
<div class=\"form-group col-md-6\">\
<label for=\"rfidIdMusic\">RFID-Chip-Nummer</label>\
<input type=\"text\" class=\"form-control\" id=\"rfidIdMusic\" maxlength=\"12\" pattern=\"[0-9]{12}\" placeholder=\"%RFID_TAG_ID%\" name=\"rfidIdMusic\" required>\
<label for=\"fileOrUrl\">Datei, Verzeichnis oder URL (URL nur für Webradio)</label>\
<input type=\"text\" class=\"form-control\" id=\"fileOrUrl\" maxlength=\"255\" placeholder=\"z.B. /mp3/Hoerspiele/Yakari/Yakari_und_seine_Freunde.mp3\" name=\"fileOrUrl\" required>\
<label for=\"playMode\">Abspielmodus</label>\
<select class=\"form-control\" id=\"playMode\" name=\"playMode\">\
<option value=\"1\">Einzelner Titel</option>\
<option value=\"2\">Einzelner Titel (Endlosschleife)</option>\
<option value=\"3\">Hörbuch</option>\
<option value=\"4\">Hörbuch (Endlosschleife)</option>\
<option value=\"5\">Alle Titel eines Verzeichnis (sortiert)</option>\
<option value=\"6\">Alle Titel eines Verzeichnis (zufällig)</option>\
<option value=\"7\">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option>\
<option value=\"8\">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>\
<option value=\"9\">Webradio</option>\
</select>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
</form>\
<div class=\"messages col-md-6 my-2\"></div>\
</div>\
<div class=\"container my-5\" id=\"rfidModTags\">\
<h2>RFID-Modifkationen</h2>\
<form class=\"needs-validation\" action=\"#rfidModTags\" method=\"POST\" onsubmit=\"rfidMods('rfidModTags'); return false\" novalidate>\
<div class=\"form-group col-md-6\">\
<label for=\"rfidIdMod\">RFID-Chip-Nummer</label>\
<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\">\
Bitte eine 12-stellige Zahl eingeben.\
</div>\
<label for=\"modId\">Abspielmodus</label>\
<select class=\"form-control\" id=\"modId\" name=\"modId\">\
<option value=\"100\">Tastensperre</option>\
<option value=\"101\">Schlafen nach 15 Minuten</option>\
<option value=\"102\">Schlafen nach 30 Minuten</option>\
<option value=\"103\">Schlafen nach 1 Stunde</option>\
<option value=\"104\">Schlafen nach 2 Stunden</option>\
<option value=\"105\">Schlafen nach Ende des Titels</option>\
<option value=\"106\">Schlafen nach Ende der Playlist</option>\
<option value=\"110\">Wiederhole Playlist (endlos)</option>\
<option value=\"111\">Wiederhole Titel (endlos)</option>\
<option value=\"112\">Dimme LEDs (Nachtmodus)</option>\
</select>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
</form>\
<div class=\"messages col-md-6 my-2\"></div>\
</div>\
<div class=\"container my-5\" id=\"mqttConfig\">\
<h2>MQTT-Konfiguration</h2>\
<form class=\"needs-validation\" action=\"#mqttConfig\" method=\"POST\" onsubmit=\"mqttSettings('mqttConfig'); return false\" novalidate>\
<div class=\"form-check col-md-6\">\
<input class=\"form-check-input\" type=\"checkbox\" value=\"1\" id=\"mqttEnable\" name=\"mqttEnable\" %MQTT_ENABLE%>\
<label class=\"form-check-label\" for=\"mqttEnable\">\
MQTT aktivieren\
</label>\
</div>\
<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,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$\" 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>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
</form>\
<div class=\"messages col-md-6 my-2\"></div>\
</div>\
<div class=\"container\" id=\"ftpConfig\">\
<h2>FTP-Konfiguration</h2>\
<form action=\"#ftpConfig\" method=\"POST\" onsubmit=\"ftpSettings('ftpConfig'); return false\">\
<div class=\"form-group col-md-6\">\
<label for=\"ftpUser\">FTP-Benutzername:</label>\
<input type=\"text\" class=\"form-control\" id=\"ftpUser\" maxlength=\"32\" placeholder=\"Benutzername\" name=\"ftpUser\" value=\"%FTP_USER%\" required>\
<label for=\"pwd\">Passwort:</label>\
<input type=\"password\" class=\"form-control\" id=\"ftpPwd\" maxlength=\"32\" placeholder=\"Passwort\" name=\"ftpPwd\" value=\"%FTP_PWD%\" required>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
</form>\
<div class=\"messages col-md-6 my-2\"></div>\
</div>\
<div class=\"container my-5\" id=\"generalConfig\">\
<h2>Allgemeine Konfiguration</h2>\
<form action=\"#generalConfig\" method=\"POST\" onsubmit=\"genSettings('generalConfig'); return false\">\
<div class=\"form-group col-md-6\">\
<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>\
<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>\
</div>\
<div class=\"form-group col-md-6\">\
<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>\
<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>\
</div>\
<div class=\"form-group col-md-6\">\
<label for=\"inactivityTime\">Deep-Sleep nach Inaktivität (Minuten)</label>\
<input type=\"number\" min=\"1\" max=\"1440\" class=\"form-control\" id=\"inactivityTime\" name=\"inactivityTime\" value=\"%MAX_INACTIVITY%\" required>\
</div>\
<button type=\"reset\" class=\"btn btn-secondary\">Reset</button>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
</form>\
<div class=\"messages col-md-6 my-2\"></div>\
</div>\
<script>\
var lastIdclicked = '';\
var errorBox = '<div class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\">Es ist ein Fehler aufgetreten!<button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button></div>';\
var okBox = '<div class=\"alert alert-success alert-dismissible fade show\" role=\"alert\">Aktion erfolgreich ausgeführt.<button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"><span aria-hidden=\"true\">&times;</span></button></div>';\
\
(function() {\
'use strict';\
window.addEventListener('load', function() {\
/* Fetch all the forms we want to apply custom Bootstrap validation styles to*/\
var forms = document.getElementsByClassName('needs-validation');\
/* Loop over them and prevent submission*/\
var validation = Array.prototype.filter.call(forms, function(form) {\
form.addEventListener('submit', function(event) {\
if (form.checkValidity() === false) {\
event.preventDefault();\
event.stopPropagation();\
}\
form.classList.add('was-validated');\
}, false);\
});\
}, false);\
});\
\
let socket = new WebSocket(\"ws://%IPv4%:81/ws\");\
socket.onmessage = function(event) {\
console.log(event.data);\
var socketMsg = JSON.parse(event.data);\
if (socketMsg.rfidId != null) {\
document.getElementById('rfidIdMod').value = socketMsg.rfidId;\
document.getElementById('rfidIdMusic').value = socketMsg.rfidId;\
$(\"#rfidIdMusic\").fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500);\
$(\"#rfidIdMod\").fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500).fadeOut(500).fadeIn(500);\
\
} if (socketMsg.status != null) {\
if (socketMsg.status == 'ok') {\
$(\"#\" + lastIdclicked).find('.messages').html(okBox);\
} else {\
$(\"#\" + lastIdclicked).find('.messages').html(errorBox);\
}\
}\
};\
\
function genSettings(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"general\": {\
iVol: document.getElementById('initialVolume').value,\
mVol: document.getElementById('maxVolume').value,\
iBright: document.getElementById('initBrightness').value,\
nBright: document.getElementById('nightBrightness').value,\
iTime: document.getElementById('inactivityTime').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function ftpSettings(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"ftp\": {\
ftpUser: document.getElementById('ftpUser').value,\
ftpPwd: document.getElementById('ftpPwd').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function mqttSettings(clickedId) {\
lastIdclicked = clickedId;\
var val;\
if (document.getElementById('mqttEnable').checked) {\
val = document.getElementById('mqttEnable').value;\
} else {\
val = 0;\
}\
var myObj = {\
\"mqtt\": {\
mqttEnable: val,\
mqttServer: document.getElementById('mqttServer').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function rfidMods(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"rfidMod\": {\
rfidIdMod: document.getElementById('rfidIdMod').value,\
modId: document.getElementById('modId').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function rfidAssign(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"rfidAssign\": {\
rfidIdMusic: document.getElementById('rfidIdMusic').value,\
fileOrUrl: document.getElementById('fileOrUrl').value,\
playMode: document.getElementById('playMode').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function wifiConfig(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"wifiConfig\": {\
ssid: document.getElementById('ssid').value,\
pwd: document.getElementById('pwd').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
</script>\
</body>\
</html>\
";

4
svg/README.md

@ -1,2 +1,2 @@
holes_n_ring.svg: Used to CNC a ring and the holes into a wood-enclosure<br />
inner ring.svg: Used to cnc a ring that the speaker is screwed to. The ring is glued to the enclosure.
`holes_n_ring.svg`: Used to CNC a ring and the holes into a wood-enclosure<br />
`inner ring.svg`: Used to cnc a ring that the speaker is screwed to. The ring is glued to the enclosure.
Loading…
Cancel
Save