Browse Source

Merge branch 'devel'

master
Torsten Stauder 5 years ago
parent
commit
d5af8f5d13
  1. 6
      .gitignore
  2. 878
      html/website.html
  3. 263
      html/websiteMgmt.h
  4. 6
      processHtml.py
  5. 256
      src/main.cpp
  6. 878
      src/websiteMgmt.h

6
.gitignore

@ -4,3 +4,9 @@
.vscode/launch.json
.vscode/ipch
.DS_Store
.idea/
CMakeLists.txt
CMakeListsPrivate.txt
cmake-build-az-delivery-devkit-v4/
cmake-build-debug/
venv/

878
html/website.html

@ -1,287 +1,538 @@
<!DOCTYPE html>
<html lang="de">
<head>
<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/tonuino/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css"/>
<script src="https://ts-cs.de/tonuino/js/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.min.js"></script>
<script src="https://ts-cs.de/tonuino/js/popper.min.js"></script>
<script src="https://ts-cs.de/tonuino/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">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<style type="text/css">
.filetree {
border: 1px solid black;
height: 200px;
margin: 0em 0em 1em 0em;
overflow-y: scroll;
}
.fa-sync:hover {
color: #666666;
}
.clickForRefresh {
text-align: center;
color: gray;
cursor: pointer;
margin-top: 5em;
}
.clickForRefresh:hover {
color: darkgray;
}
.filetree-container {
position: relative;
}
.indexing-progress {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
opacity: 0.7;
display: none;
}
.refreshAction{
text-align: right;
font-size: 0.8em;
}
.refreshAction:hover{
cursor: pointer;
color: darkgray;
}
.overlay {
z-index: 9;
opacity: 0.8;
background: #1a1919;
height: 200px;
display: none;
width: 100%;
}
</style>
</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="https://raw.githubusercontent.com/biologist79/Tonuino-ESP32-I2S/master/html/tonuino_logo.png" width="30" height="30" class="d-inline-block align-top" alt="" />
</button>
<a class="navbar-brand">
<img src="https://raw.githubusercontent.com/biologist79/Tonuino-ESP32-I2S/master/html/tonuino_logo.png"
width="30" height="30" class="d-inline-block align-top" alt=""/>
Tonuino
</a>
<div class="collapse navbar-collapse" id="collapsibleNavbar">
</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>
<li class="nav-item">
<a class="nav-link" href="#importNvs">NVS-Importer</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/restart" style="color: orange">Neustart Tonuino</a>
</li>
<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>
<li class="nav-item">
<a class="nav-link" href="#importNvs">NVS-Importer</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/restart" style="color: orange">Neustart Tonuino</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>
</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>
<label for="hostname">Tonuino-Name (Hostname):</label>
<input type="text" class="form-control" id="hostname" placeholder="tonuino" name="hostname" value="%HOSTNAME%" pattern="^[^-\.]{2,32}" required>
<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>
<label for="hostname">Tonuino-Name (Hostname):</label>
<input type="text" class="form-control" id="hostname" placeholder="tonuino" name="hostname"
value="%HOSTNAME%" pattern="^[^-\.]{2,32}" 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 (12-stellig)</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 (^ und # als Zeichen nicht erlaubt)</label>
<input type="text" class="form-control" id="fileOrUrl" maxlength="255" placeholder="z.B. /mp3/Hoerspiele/Yakari/Yakari_und_seine_Freunde.mp3" pattern="^[^\^#]+$" 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="9">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>
<option value="8">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">
<div class="form-group col-md-6">
<label for="rfidIdMod">RFID-Chip-Nummer (12-stellig)</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.
</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 (12-stellig)</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 (^ und # als Zeichen nicht erlaubt)</label>
<input type="text" class="form-control" id="fileOrUrl" maxlength="255" placeholder="z.B. /mp3/Hoerspiele/Yakari/Yakari_und_seine_Freunde.mp3" pattern="^[^\^#]+$" name="fileOrUrl" required>
<div class="filetree-container">
<div id="filebrowser">
<div class="filetree demo" id="filetree"></div>
<div class="refreshAction">
<span id="refreshAction" data-toggle="tooltip" data-placement="top" title="Datei Liste aktualisieren."><i class="fas fa-sync fa-1x"></i> Dateien suchen</span>
</div>
</div>
<div id="indexing-progress" class="indexing-progress overlay">
<div style="text-align: center; color:white; margin-top:2em;">
<div><i class="fas fa-sync fa-spin fa-2x"></i> <br><br> Der Prozess kann mehrere Minuten dauern...</div>
<div id="currentProcessedFile"></div>
</div>
</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="107">Schlafen nach fünf Titeln</option>
<option value="110">Wiederhole Playlist (endlos)</option>
<option value="111">Wiederhole Titel (endlos)</option>
<option value="112">Dimme LEDs (Nachtmodus)</option>
<option value="130">Aktiviere/deaktive WLAN</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">
<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</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>
<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>
<input type="password" class="form-control" id="mqttPwd" maxlength="%MQTT_PWD_LENGTH%" placeholder="Passwort" name="mqttPwd" value="%MQTT_PWD%">
<label for="playMode">Abspielmodus</label>
<select class="form-control" id="playMode" name="playMode">
<option class="option-file" value="1">Einzelner Titel</option>
<option class="option-file" value="2">Einzelner Titel (Endlosschleife)</option>
<option class="option-folder" value="3">Hörbuch</option>
<option class="option-folder" value="4">Hörbuch (Endlosschleife)</option>
<option class="option-folder" value="5">Alle Titel eines Verzeichnis (sortiert)</option>
<option class="option-folder" value="6">Alle Titel eines Verzeichnis (zufällig)</option>
<option class="option-folder" value="7">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option>
<option class="option-folder" value="9">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>
<option class="option-stream" value="8">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">
<div class="form-group col-md-6">
<label for="rfidIdMod">RFID-Chip-Nummer (12-stellig)</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>
<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="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="107">Schlafen nach fünf Titeln</option>
<option value="110">Wiederhole Playlist (endlos)</option>
<option value="111">Wiederhole Titel (endlos)</option>
<option value="112">Dimme LEDs (Nachtmodus)</option>
<option value="130">Aktiviere/deaktive WLAN</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">
<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</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>
<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>
<input type="password" class="form-control" id="mqttPwd" maxlength="%MQTT_PWD_LENGTH%"
placeholder="Passwort" name="mqttPwd" value="%MQTT_PWD%">
</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="%FTP_USER_LENGTH%" 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>
<input type="password" class="form-control" id="ftpPwd" maxlength="%FTP_PWD_LENGTH%" 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="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 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_BRIGHTNESS%" 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_BRIGHTNESS%" 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>
<div class="form-group col-md-6">
<label for="warningLowVoltage">(Optional) Spannungsgrenze (Batterie), ab der Warnung auf Neopixel angezeigt wird (z.B. 3.4)</label>
<input type="text" class="form-control" id="warningLowVoltage" name="warningLowVoltage" value="%WARNING_LOW_VOLTAGE%" pattern="^\d{1,2}(\.\d{1,3})?" required>
</div>
<div class="form-group col-md-6">
<label for="voltageIndicatorLow">(Optional) Unterer Akkuspannungslevel (Batterie) für Neopixel-Anzeige (z.B. 3.1)</label>
<input type="text" class="form-control" id="voltageIndicatorLow" name="voltageIndicatorLow" value="%VOLTAGE_INDICATOR_LOW%" pattern="^\d{1,2}(\.\d{1,3})?" required>
</div>
<div class="form-group col-md-6">
<label for="voltageIndicatorHigh">(Optional) Oberer Akkuspannungslevel (Batterie) für Neopixel-Anzeige (z.B. 4.2)</label>
<input type="text" class="form-control" id="voltageIndicatorHigh" name="voltageIndicatorHigh" value="%VOLTAGE_INDICATOR_HIGH%" pattern="^\d{1,2}(\.\d{1,3})?" required>
</div>
<div class="form-group col-md-6">
<label for="voltageCheckInterval">(Optional) Häufigkeit der Spannungsmessung (Batterie) in Minuten</label>
<input type="number" min="1" max="180" class="form-control" id="voltageCheckInterval" name="voltageCheckInterval" value="%VOLTAGE_CHECK_INTERVAL%" 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="importNvs">
<h2>NVS-Importer</h2>
<form action="/upload" enctype="multipart/form-data" method="POST">
<div class="form-group">
<input type="password" class="form-control" id="ftpPwd" maxlength="%FTP_PWD_LENGTH%" 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="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 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_BRIGHTNESS%" 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_BRIGHTNESS%" 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>
<div class="form-group col-md-6">
<label for="warningLowVoltage">(Optional) Spannungsgrenze (Batterie), ab der Warnung auf Neopixel angezeigt
wird (z.B. 3.4)</label>
<input type="text" class="form-control" id="warningLowVoltage" name="warningLowVoltage"
value="%WARNING_LOW_VOLTAGE%" pattern="^\d{1,2}(\.\d{1,3})?" required>
</div>
<div class="form-group col-md-6">
<label for="voltageIndicatorLow">(Optional) Unterer Akkuspannungslevel (Batterie) für Neopixel-Anzeige (z.B.
3.1)</label>
<input type="text" class="form-control" id="voltageIndicatorLow" name="voltageIndicatorLow"
value="%VOLTAGE_INDICATOR_LOW%" pattern="^\d{1,2}(\.\d{1,3})?" required>
</div>
<div class="form-group col-md-6">
<label for="voltageIndicatorHigh">(Optional) Oberer Akkuspannungslevel (Batterie) für Neopixel-Anzeige (z.B.
4.2)</label>
<input type="text" class="form-control" id="voltageIndicatorHigh" name="voltageIndicatorHigh"
value="%VOLTAGE_INDICATOR_HIGH%" pattern="^\d{1,2}(\.\d{1,3})?" required>
</div>
<div class="form-group col-md-6">
<label for="voltageCheckInterval">(Optional) Häufigkeit der Spannungsmessung (Batterie) in Minuten</label>
<input type="number" min="1" max="180" class="form-control" id="voltageCheckInterval"
name="voltageCheckInterval" value="%VOLTAGE_CHECK_INTERVAL%" 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="importNvs">
<h2>NVS-Importer</h2>
<form action="/upload" enctype="multipart/form-data" method="POST">
<div class="form-group">
<label for="nvsUpload">Hier kann eine Backup-Datei importiert werden.</label>
<input type="file" class="form-control-file" id="nvsUpload" name="nvsUpload" accept=".txt">
</div>
<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>';
var socket = new WebSocket("ws://%IPv4%/ws");
function connect() {
socket = new WebSocket("ws://%IPv4%/ws");
}
</div>
<button type="submit" class="btn btn-primary">Absenden</button>
</form>
<div class="messages col-md-6 my-2"></div>
</div>
<script type="text/javascript">
var DEBUG = false;
var lastIdclicked = '';
var host = $(location).attr('hostname');
function ping() {
var myObj = {
"ping": {
ping: 'ping'
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
tm = setTimeout(function () {
alert("Die Verbindung zum Tonuino ist unterbrochen!\\nBitte Seite neu laden.");
}, 5000);
}
if (DEBUG) {
host = "192.168.178.112";
}
function pong() {
clearTimeout(tm);
}
toastr.options = {
"closeButton": false,
"debug": false,
"newestOnTop": false,
"progressBar": false,
"positionClass": "toast-top-right",
"preventDuplicates": false,
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
};
function postRendering(event, data) {
Object.keys(data.instance._model.data).forEach(function (key, index) {
var cur = data.instance.get_node(data.instance._model.data[key]);
var lastFolder = cur['id'].split('/').filter(function (el) {
return el.trim().length > 0;
}).pop();
if ((/\.(mp3|MP3|ogg|wav|WAV|OGG|wma|WMA|acc|ACC|flac|FLAC)$/i).test(lastFolder)) {
data.instance.set_type(data.instance._model.data[key], 'audio');
} else {
if (data.instance._model.data[key]['type'] == "file") {
data.instance.disable_node(data.instance._model.data[key]);
}
}
data.instance.rename_node(data.instance._model.data[key], lastFolder);
});
}
function renderFileTree() {
var filesURI = "/files";
if (DEBUG) {
filesURI = "http://" + host + "/files";
}
$('#filetree').jstree({
'core': {
'check_callback': true,
'data': {
url: filesURI,
error: function (XMLHttpRequest, textStatus, errorThrown) {
$('#j1_loading').hide();
$("#refreshAction").hide();
$('#filetree').html("<div class='clickForRefresh' ><i class='fas fa-sync fa-1x'><span id='#clickForRefresh' > Dateien suchen.</span></div>");
$('#filetree').on("click", function () {
refreshFileList();
});
toastr.error("Die Verzeichnis Liste konnte nicht geladen werden.");
}
},
},
'types': {
'folder': {
'icon': "fa fa-folder"
},
'file': {
'icon': "fa fa-file"
},
'audio': {
'icon': "fa fa-file-audio"
},
'default': {
'icon': "fa fa-folder"
}
},
'plugins': ["themes", "types"]
}).bind('loaded.jstree', function (event, data) {
postRendering(event, data);
if ((data.instance._model.data['#']['children'].length == 0)) {
toastr.info("Der SD Karten Index muss erzeugt werden.");
}
}).bind('refresh.jstree', function (event, data) {
postRendering(event, data);
});
}
$('#filetree').on('select_node.jstree', function (e, data) {
$('input[name=fileOrUrl]').val(data.node.id);
if (data.node.type == "folder") {
$('.option-folder').show();
$('.option-file').hide();
$('#playMode option').removeAttr('selected').filter('[value=3]').attr('selected', true);
}
socket.onopen = function () {
if (data.node.type == "audio") {
$('.option-file').show();
$('.option-folder').hide();
$('#playMode option').removeAttr('selected').filter('[value=1]').attr('selected', true);
}
});
$('#refreshAction').on("click", function () {
refreshFileList();
$("#indexing-progress").show();
$("#refreshAction").hide();
});
$('#playMode').on("change", function () {
if (this.value == 8) {
$('#filebrowser').slideUp();
} else {
$('#filebrowser').slideDown();
}
});
function showFileIndexingState(){
$("#indexing-progress").show();
$("#refreshAction").hide();
}
function hideFileIndexingState(){
$("#indexing-progress").hide();
$("#refreshAction").show();
}
var socket = undefined;
var tm;
function connect() {
socket = new WebSocket("ws://" + host + "/ws");
socket.onopen = function () {
setInterval(ping, 15000);
};
};
socket.onclose = function(e) {
socket.onclose = function (e) {
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
setTimeout(function() {
connect();
socket = null;
setTimeout(function () {
connect();
}, 5000);
};
};
socket.onerror = function(err) {
socket.onerror = function (err) {
console.error('Socket encountered error: ', err.message, 'Closing socket');
socket.close();
};
};
socket.onmessage = function(event) {
console.log(event.data);
var socketMsg = JSON.parse(event.data);
if (socketMsg.rfidId != null) {
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);
}
} if (socketMsg.pong != null) {
if (socketMsg.pong == 'pong') {
toastr.info("RFID Tag mit "+ socketMsg.rfidId + " erkannt." );
$("#rfidIdMusic").effect("highlight", {color:"#abf5af"}, 3000);
$("#rfidIdMod").effect("highlight", {color:"#abf5af"}, 3000);
} if ("status" in socketMsg) {
if (socketMsg.status == "ok") {
toastr.success("Aktion erfolgreich ausgeführt." );
}
} if ("pong" in socketMsg) {
if (socketMsg.pong == 'pong') {
pong();
}
}
} if ("refreshFileList" in socketMsg){
hideFileIndexingState();
toastr.info("Die Datei Liste wurde neu erzeugt!");
$('#filetree').jstree(true).refresh();
}
if ("indexingState" in socketMsg){
if(socketMsg.indexingState != null) {
$("#currentProcessedFile").text(socketMsg.indexingState);
console.log(socketMsg.indexingState);
}
}
};
}
function ping() {
var myObj = {
"ping": {
ping: 'ping'
}
};
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
tm = setTimeout(function () {
toastr.warning('Die Verbindung zum Tonuino ist unterbrochen! Bitte Seite neu laden.');
}, 5000);
}
function genSettings(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"general": {
function pong() {
clearTimeout(tm);
}
function refreshFileList(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"refreshFileList": true
};
var myJSON = JSON.stringify(myObj);
$("#refreshAction").hide();
socket.send(myJSON);
showFileIndexingState();
};
function genSettings(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"general": {
iVol: document.getElementById('initialVolume').value,
mVolSpeaker: document.getElementById('maxVolumeSpeaker').value,
mVolHeadphone: document.getElementById('maxVolumeHeadphone').value,
@ -292,88 +543,97 @@
vIndLow: document.getElementById('voltageIndicatorLow').value,
vIndHi: document.getElementById('voltageIndicatorHigh').value,
vInt: document.getElementById('voltageCheckInterval').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function ftpSettings(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"ftp": {
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": {
};
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,
mqttUser: document.getElementById('mqttUser').value,
mqttPwd: document.getElementById('mqttPwd').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function rfidMods(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"rfidMod": {
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 removeTrSlash(str) {
if(str.substr(-1) === '/') {
return str.substr(0, str.length - 1);
}
return str;
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function rfidAssign(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"rfidAssign": {
function removeTrSlash(str) {
if (str.substr(-1) === '/') {
return str.substr(0, str.length - 1);
}
return str;
}
function rfidAssign(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"rfidAssign": {
rfidIdMusic: document.getElementById('rfidIdMusic').value,
fileOrUrl: removeTrSlash(document.getElementById('fileOrUrl').value),
playMode: document.getElementById('playMode').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
function wifiConfig(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"wifiConfig": {
function wifiConfig(clickedId) {
lastIdclicked = clickedId;
var myObj = {
"wifiConfig": {
ssid: document.getElementById('ssid').value,
pwd: document.getElementById('pwd').value,
hostname: document.getElementById('hostname').value
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
</script>
</body>
}
};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON);
}
$(document).ready(function () {
connect();
renderFileTree();
$(function () {
$('[data-toggle="tooltip"]').tooltip();
});
});
</script>
</body>
</html>

263
html/websiteMgmt.h

@ -1,263 +0,0 @@
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>\
";

6
processHtml.py

@ -1,16 +1,20 @@
#!/usr/bin/python
import re
content = ''
content2 = ''
contentEN = ''
content2EN = ''
# TODO: Add a JS Minifier python lib
with open('html/website.html', 'r') as r:
data = r.read().replace('\n', '\\\n')
data = r.read()
data = data.replace('\n', '\\\n')
data = data.replace('\"', '\\"')
data = data.replace('\\d', '\\\d')
data = data.replace('\\.', '\\\.')
data = data.replace('\\^', '\\\\^')
data = data.replace('%;', '%%;')
content += data
with open('src/websiteMgmt.h', 'w') as w:

256
src/main.cpp

@ -67,12 +67,16 @@
#define LOGLEVEL_DEBUG 4 // almost everything
// Serial-logging-configuration
const uint8_t serialDebug = LOGLEVEL_INFO; // Current loglevel for serial console
const uint8_t serialDebug = LOGLEVEL_DEBUG; // Current loglevel for serial console
// Serial-logging buffer
uint8_t serialLoglength = 200;
char *logBuf = (char*) calloc(serialLoglength, sizeof(char)); // Buffer for all log-messages
// File Browser
uint8_t FS_DEPTH = 3; // max recursion depth of file tree
const char * DIRECTORY_INDEX_FILE = "/files.json"; // filename of files.json index file
// GPIOs (uSD card-reader)
#define SPISD_CS 15
#ifndef SINGLE_SPI_ENABLE
@ -131,8 +135,8 @@ float voltageIndicatorHigh = 4.2; // Upper range for Neopixel-
#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!)
uint16_t r1 = 389; // First resistor of voltage-divider (kOhms) (measure exact value with multimeter!)
uint8_t r2 = 129; // Second resistor of voltage-divider (kOhms) (measure exact value with multimeter!)
// Internal values
float refVoltage = 3.3; // Operation-voltage of ESP32; don't change!
@ -523,6 +527,209 @@ void IRAM_ATTR onTimer() {
}
#endif
/**
* Creates a new file on the SD Card.
* @param fs
* @param path
* @param message
*/
void createFile(fs::FS &fs, const char * path, const char * message) {
snprintf(logBuf, serialLoglength, "Writing file: %s\n", path);
loggerNl(logBuf, LOGLEVEL_DEBUG);
File file = fs.open(path, FILE_WRITE);
if(!file){
snprintf(logBuf, serialLoglength, "Failed to open file for writing");
loggerNl(logBuf, LOGLEVEL_ERROR);
return;
}
if(file.print(message)){
snprintf(logBuf, serialLoglength, "File written");
loggerNl(logBuf, LOGLEVEL_DEBUG);
} else {
Serial.println("Write failed");
snprintf(logBuf, serialLoglength, "Write failed");
loggerNl(logBuf, LOGLEVEL_ERROR);
}
file.close();
}
bool fileExists(fs::FS &fs, const char *file) {
return fs.exists(file);
}
/**
* Appends raw input to a file
* @param fs
* @param path
* @param text
*/
void appendToFile(fs::FS &fs, const char *path, const char *text) {
File file = fs.open(path, FILE_APPEND);
esp_task_wdt_reset();
file.print(text);
file.close();
}
// indicates if the given node is first node of file
bool isFirstJSONtNode = true;
/**
* Helper function for writing file index to json file.
* This function appends a new json node for files/directories to
* a given file.
* @param fs
* @param path
* @param filename
* @param parent
* @param type
*/
void appendNodeToJSONFile(fs::FS &fs, const char * path, const char *filename, const char *parent, const char *type ) {
// Serial.printf("Appending to file: %s\n", path);
snprintf(logBuf, serialLoglength, "Listing directory: %s\n", filename);
loggerNl(logBuf, LOGLEVEL_DEBUG);
File file = fs.open(path, FILE_APPEND);
// i/o is timing critical keep all stuff running
esp_task_wdt_reset();
if (!file) {
snprintf(logBuf, serialLoglength, "Failed to open file for appending");
loggerNl(logBuf, LOGLEVEL_DEBUG);
return;
}
if (!isFirstJSONtNode) {
file.print(",");
}
//TODO: write a minified json, without all those whitespaces
// it is just easier to debug when json is in a nice format
// anyway ugly but works and is stable
file.print(F(( " {\n \"id\" : \"")));
file.print(filename);
file.print(F("\",\n \"parent\" : \""));
file.print(parent);
file.print(F("\",\n \"type\": \""));
file.print(type);
file.print(F("\",\n \"text\" : \""));
file.print(filename);
file.print(F("\"\n }"));
// i/o is timing critical keep all stuff running
esp_task_wdt_reset();
yield();
file.close();
if (isFirstJSONtNode) {
isFirstJSONtNode = false;
}
}
/**
* Checks if a path is valid. (e.g. hidden path is not valid)
* @param _fileItem
* @return
*/
bool pathValid(const char *_fileItem) {
const char ch = '/';
char *subst;
subst = strrchr(_fileItem, ch); // Don't use files that start with .
return (!startsWith(subst, (char *) "/."));
}
/**
* SD-Card index parser. Parses the SD Card directories
* by a given file path depth recursive and appends the
* found files and directories to files.json file.
* @param fs
* @param dirname
* @param parent
* @param levels
*/
char fileNameBuf[255];
void parseSDFileList(fs::FS &fs, const char * dirname, const char * parent, uint8_t levels) {
esp_task_wdt_reset();
yield();
File root = fs.open(dirname);
if(!root){
snprintf(logBuf, serialLoglength, "Failed to open directory");
loggerNl(logBuf, LOGLEVEL_DEBUG);
return;
}
if(!root.isDirectory()){
snprintf(logBuf, serialLoglength, "Not a directory");
loggerNl(logBuf, LOGLEVEL_DEBUG);
return;
}
File file = root.openNextFile();
while(file){
esp_task_wdt_reset();
const char *parent;
if (strcmp(root.name(), "/") == 0 || root.name() == 0){
parent = "#\0";
} else {
parent = root.name();
}
if (file.name() == 0 ){
continue;
}
strncpy(fileNameBuf, (char *) file.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0]));
// we have a folder
if(file.isDirectory()){
esp_task_wdt_reset();
if (pathValid(fileNameBuf)){
sendWebsocketData(0, 31);
appendNodeToJSONFile(SD, DIRECTORY_INDEX_FILE, fileNameBuf, parent, "folder" );
// check for next subfolder
if(levels){
parseSDFileList(fs, fileNameBuf, root.name(), levels -1);
}
}
// we have a file
} else {
if (fileValid(fileNameBuf)){
appendNodeToJSONFile(SD, DIRECTORY_INDEX_FILE, fileNameBuf, parent, "file" );
}
}
vTaskDelay(portTICK_PERIOD_MS*50);
file = root.openNextFile();
// i/o is timing critical keep all stuff running
esp_task_wdt_reset();
}
}
/**
* Public function for creating file index json on SD-Card.
* It notifies the user client via websockets when the indexing
* is done.
*/
void createJSONFileList() {
createFile(SD, DIRECTORY_INDEX_FILE, "[\n");
parseSDFileList(SD, "/", NULL, FS_DEPTH);
appendToFile(SD, DIRECTORY_INDEX_FILE, "]");
isFirstJSONtNode = true;
sendWebsocketData(0,30);
}
void fileHandlingTask(void *arguments) {
createJSONFileList();
esp_task_wdt_reset();
vTaskDelete( NULL );
}
// Measures voltage of a battery as per interval or after bootup (after allowing a few seconds to settle down)
#ifdef MEASURE_BATTERY_VOLTAGE
float measureBatteryVoltage(void) {
@ -2792,6 +2999,8 @@ void accessPointStart(const char *SSID, IPAddress ip, IPAddress netmask) {
ESP.restart();
});
// allow cors for local debug
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
wServer.begin();
loggerNl((char *) FPSTR(httpReady), LOGLEVEL_NOTICE);
accessPointStarted = true;
@ -3093,6 +3302,19 @@ bool processJsonRequest(char *_serialJson) {
}
} else if (doc.containsKey("ping")) {
sendWebsocketData(0, 20);
return false;
} else if (doc.containsKey("refreshFileList")) {
//TODO: we need a semaphore or mutex here to prevent
// a call when the task is still running
xTaskCreate(
fileHandlingTask, /* Task function. */
"TaskTwo", /* String with name of task. */
10000, /* Stack size in bytes. */
NULL, /* Parameter passed as input of the task */
1, /* Priority of the task. */
NULL); /* Task handle. */
}
return true;
@ -3113,8 +3335,13 @@ void sendWebsocketData(uint32_t client, uint8_t code) {
object["rfidId"] = currentRfidTagId;
} else if (code == 20) {
object["pong"] = "pong";
} else if (code == 30){
object["refreshFileList"] = "ready";
}else if (code == 31){
object["indexingState"] = fileNameBuf;
}
char jBuf[50];
char jBuf[255];
serializeJson(doc, jBuf, sizeof(jBuf) / sizeof(jBuf[0]));
if (client == 0) {
@ -3147,14 +3374,10 @@ void onWebsocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsE
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);
}
sendWebsocketData(client->id(), 1);
if (info->opcode == WS_TEXT) {
data[len] = 0;
@ -3251,7 +3474,14 @@ void webserverStart(void) {
ESP.restart();
});
wServer.on("/files", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SD, "/files.json", "application/json");
});
wServer.onNotFound(notFound);
// allow cors for local debug
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
wServer.begin();
webserverStarted = true;
}
@ -3721,6 +3951,13 @@ void setup() {
lastTimeActiveTimestamp = millis(); // initial set after boot
/**
* Create empty Index json file when no file exists.
*/
if(!fileExists(SD,DIRECTORY_INDEX_FILE)){
createFile(SD,DIRECTORY_INDEX_FILE,"[]");
ESP.restart();
}
bootComplete = true;
Serial.print(F("Free heap: "));
@ -3762,6 +3999,7 @@ void loop() {
#ifdef PLAY_LAST_RFID_AFTER_REBOOT
recoverLastRfidPlayed();
#endif
ws.cleanupClients();
}

878
src/websiteMgmt.h

@ -1,287 +1,538 @@
static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
<html lang=\"de\">\
<head>\
<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/tonuino/css/bootstrap.min.css\">\
<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css\"/>\
<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css\"/>\
<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css\"/>\
<script src=\"https://ts-cs.de/tonuino/js/jquery.min.js\"></script>\
<script src=\"https://code.jquery.com/ui/1.12.0/jquery-ui.min.js\"></script>\
<script src=\"https://ts-cs.de/tonuino/js/popper.min.js\"></script>\
<script src=\"https://ts-cs.de/tonuino/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\">\
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js\"></script>\
<script src=\"https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js\"></script>\
<style type=\"text/css\">\
.filetree {\
border: 1px solid black;\
height: 200px;\
margin: 0em 0em 1em 0em;\
overflow-y: scroll;\
}\
\
.fa-sync:hover {\
color: #666666;\
}\
\
.clickForRefresh {\
text-align: center;\
color: gray;\
cursor: pointer;\
margin-top: 5em;\
}\
\
.clickForRefresh:hover {\
color: darkgray;\
}\
\
.filetree-container {\
position: relative;\
}\
\
.indexing-progress {\
width: 100%%;\
height: 100%%;\
position: absolute;\
top: 0;\
left: 0;\
opacity: 0.7;\
display: none;\
}\
\
.refreshAction{\
text-align: right;\
font-size: 0.8em;\
}\
\
.refreshAction:hover{\
cursor: pointer;\
color: darkgray;\
}\
\
.overlay {\
z-index: 9;\
opacity: 0.8;\
background: #1a1919;\
height: 200px;\
display: none;\
width: 100%%;\
}\
</style>\
</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=\"https://raw.githubusercontent.com/biologist79/Tonuino-ESP32-I2S/master/html/tonuino_logo.png\" width=\"30\" height=\"30\" class=\"d-inline-block align-top\" alt=\"\" />\
</button>\
<a class=\"navbar-brand\">\
<img src=\"https://raw.githubusercontent.com/biologist79/Tonuino-ESP32-I2S/master/html/tonuino_logo.png\"\
width=\"30\" height=\"30\" class=\"d-inline-block align-top\" alt=\"\"/>\
Tonuino\
</a>\
<div class=\"collapse navbar-collapse\" id=\"collapsibleNavbar\">\
</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>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#importNvs\">NVS-Importer</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"/restart\" style=\"color: orange\">Neustart Tonuino</a>\
</li>\
<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>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"#importNvs\">NVS-Importer</a>\
</li>\
<li class=\"nav-item\">\
<a class=\"nav-link\" href=\"/restart\" style=\"color: orange\">Neustart Tonuino</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>\
</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>\
<label for=\"hostname\">Tonuino-Name (Hostname):</label>\
<input type=\"text\" class=\"form-control\" id=\"hostname\" placeholder=\"tonuino\" name=\"hostname\" value=\"%HOSTNAME%\" pattern=\"^[^-\\.]{2,32}\" required>\
<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>\
<label for=\"hostname\">Tonuino-Name (Hostname):</label>\
<input type=\"text\" class=\"form-control\" id=\"hostname\" placeholder=\"tonuino\" name=\"hostname\"\
value=\"%HOSTNAME%\" pattern=\"^[^-\\.]{2,32}\" 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 (12-stellig)</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 (^ und # als Zeichen nicht erlaubt)</label>\
<input type=\"text\" class=\"form-control\" id=\"fileOrUrl\" maxlength=\"255\" placeholder=\"z.B. /mp3/Hoerspiele/Yakari/Yakari_und_seine_Freunde.mp3\" pattern=\"^[^\\^#]+$\" 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=\"9\">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>\
<option value=\"8\">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\">\
<div class=\"form-group col-md-6\">\
<label for=\"rfidIdMod\">RFID-Chip-Nummer (12-stellig)</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.\
</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 (12-stellig)</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 (^ und # als Zeichen nicht erlaubt)</label>\
<input type=\"text\" class=\"form-control\" id=\"fileOrUrl\" maxlength=\"255\" placeholder=\"z.B. /mp3/Hoerspiele/Yakari/Yakari_und_seine_Freunde.mp3\" pattern=\"^[^\\^#]+$\" name=\"fileOrUrl\" required>\
<div class=\"filetree-container\">\
<div id=\"filebrowser\">\
<div class=\"filetree demo\" id=\"filetree\"></div>\
<div class=\"refreshAction\">\
<span id=\"refreshAction\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Datei Liste aktualisieren.\"><i class=\"fas fa-sync fa-1x\"></i> Dateien suchen</span>\
</div>\
</div>\
<div id=\"indexing-progress\" class=\"indexing-progress overlay\">\
<div style=\"text-align: center; color:white; margin-top:2em;\">\
<div><i class=\"fas fa-sync fa-spin fa-2x\"></i> <br><br> Der Prozess kann mehrere Minuten dauern...</div>\
<div id=\"currentProcessedFile\"></div>\
</div>\
</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=\"107\">Schlafen nach fünf Titeln</option>\
<option value=\"110\">Wiederhole Playlist (endlos)</option>\
<option value=\"111\">Wiederhole Titel (endlos)</option>\
<option value=\"112\">Dimme LEDs (Nachtmodus)</option>\
<option value=\"130\">Aktiviere/deaktive WLAN</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\">\
<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</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>\
<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>\
<input type=\"password\" class=\"form-control\" id=\"mqttPwd\" maxlength=\"%MQTT_PWD_LENGTH%\" placeholder=\"Passwort\" name=\"mqttPwd\" value=\"%MQTT_PWD%\">\
<label for=\"playMode\">Abspielmodus</label>\
<select class=\"form-control\" id=\"playMode\" name=\"playMode\">\
<option class=\"option-file\" value=\"1\">Einzelner Titel</option>\
<option class=\"option-file\" value=\"2\">Einzelner Titel (Endlosschleife)</option>\
<option class=\"option-folder\" value=\"3\">Hörbuch</option>\
<option class=\"option-folder\" value=\"4\">Hörbuch (Endlosschleife)</option>\
<option class=\"option-folder\" value=\"5\">Alle Titel eines Verzeichnis (sortiert)</option>\
<option class=\"option-folder\" value=\"6\">Alle Titel eines Verzeichnis (zufällig)</option>\
<option class=\"option-folder\" value=\"7\">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option>\
<option class=\"option-folder\" value=\"9\">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>\
<option class=\"option-stream\" value=\"8\">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\">\
<div class=\"form-group col-md-6\">\
<label for=\"rfidIdMod\">RFID-Chip-Nummer (12-stellig)</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>\
<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=\"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=\"107\">Schlafen nach fünf Titeln</option>\
<option value=\"110\">Wiederhole Playlist (endlos)</option>\
<option value=\"111\">Wiederhole Titel (endlos)</option>\
<option value=\"112\">Dimme LEDs (Nachtmodus)</option>\
<option value=\"130\">Aktiviere/deaktive WLAN</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\">\
<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</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>\
<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>\
<input type=\"password\" class=\"form-control\" id=\"mqttPwd\" maxlength=\"%MQTT_PWD_LENGTH%\"\
placeholder=\"Passwort\" name=\"mqttPwd\" value=\"%MQTT_PWD%\">\
</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=\"%FTP_USER_LENGTH%\" 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>\
<input type=\"password\" class=\"form-control\" id=\"ftpPwd\" maxlength=\"%FTP_PWD_LENGTH%\" 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=\"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 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_BRIGHTNESS%\" 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_BRIGHTNESS%\" 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>\
<div class=\"form-group col-md-6\">\
<label for=\"warningLowVoltage\">(Optional) Spannungsgrenze (Batterie), ab der Warnung auf Neopixel angezeigt wird (z.B. 3.4)</label>\
<input type=\"text\" class=\"form-control\" id=\"warningLowVoltage\" name=\"warningLowVoltage\" value=\"%WARNING_LOW_VOLTAGE%\" pattern=\"^\\d{1,2}(\\.\\d{1,3})?\" required>\
</div>\
<div class=\"form-group col-md-6\">\
<label for=\"voltageIndicatorLow\">(Optional) Unterer Akkuspannungslevel (Batterie) für Neopixel-Anzeige (z.B. 3.1)</label>\
<input type=\"text\" class=\"form-control\" id=\"voltageIndicatorLow\" name=\"voltageIndicatorLow\" value=\"%VOLTAGE_INDICATOR_LOW%\" pattern=\"^\\d{1,2}(\\.\\d{1,3})?\" required>\
</div>\
<div class=\"form-group col-md-6\">\
<label for=\"voltageIndicatorHigh\">(Optional) Oberer Akkuspannungslevel (Batterie) für Neopixel-Anzeige (z.B. 4.2)</label>\
<input type=\"text\" class=\"form-control\" id=\"voltageIndicatorHigh\" name=\"voltageIndicatorHigh\" value=\"%VOLTAGE_INDICATOR_HIGH%\" pattern=\"^\\d{1,2}(\\.\\d{1,3})?\" required>\
</div>\
<div class=\"form-group col-md-6\">\
<label for=\"voltageCheckInterval\">(Optional) Häufigkeit der Spannungsmessung (Batterie) in Minuten</label>\
<input type=\"number\" min=\"1\" max=\"180\" class=\"form-control\" id=\"voltageCheckInterval\" name=\"voltageCheckInterval\" value=\"%VOLTAGE_CHECK_INTERVAL%\" 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=\"importNvs\">\
<h2>NVS-Importer</h2>\
<form action=\"/upload\" enctype=\"multipart/form-data\" method=\"POST\">\
<div class=\"form-group\">\
<input type=\"password\" class=\"form-control\" id=\"ftpPwd\" maxlength=\"%FTP_PWD_LENGTH%\" 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=\"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 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_BRIGHTNESS%\" 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_BRIGHTNESS%\" 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>\
<div class=\"form-group col-md-6\">\
<label for=\"warningLowVoltage\">(Optional) Spannungsgrenze (Batterie), ab der Warnung auf Neopixel angezeigt\
wird (z.B. 3.4)</label>\
<input type=\"text\" class=\"form-control\" id=\"warningLowVoltage\" name=\"warningLowVoltage\"\
value=\"%WARNING_LOW_VOLTAGE%\" pattern=\"^\\d{1,2}(\\.\\d{1,3})?\" required>\
</div>\
<div class=\"form-group col-md-6\">\
<label for=\"voltageIndicatorLow\">(Optional) Unterer Akkuspannungslevel (Batterie) für Neopixel-Anzeige (z.B.\
3.1)</label>\
<input type=\"text\" class=\"form-control\" id=\"voltageIndicatorLow\" name=\"voltageIndicatorLow\"\
value=\"%VOLTAGE_INDICATOR_LOW%\" pattern=\"^\\d{1,2}(\\.\\d{1,3})?\" required>\
</div>\
<div class=\"form-group col-md-6\">\
<label for=\"voltageIndicatorHigh\">(Optional) Oberer Akkuspannungslevel (Batterie) für Neopixel-Anzeige (z.B.\
4.2)</label>\
<input type=\"text\" class=\"form-control\" id=\"voltageIndicatorHigh\" name=\"voltageIndicatorHigh\"\
value=\"%VOLTAGE_INDICATOR_HIGH%\" pattern=\"^\\d{1,2}(\\.\\d{1,3})?\" required>\
</div>\
<div class=\"form-group col-md-6\">\
<label for=\"voltageCheckInterval\">(Optional) Häufigkeit der Spannungsmessung (Batterie) in Minuten</label>\
<input type=\"number\" min=\"1\" max=\"180\" class=\"form-control\" id=\"voltageCheckInterval\"\
name=\"voltageCheckInterval\" value=\"%VOLTAGE_CHECK_INTERVAL%\" 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=\"importNvs\">\
<h2>NVS-Importer</h2>\
<form action=\"/upload\" enctype=\"multipart/form-data\" method=\"POST\">\
<div class=\"form-group\">\
<label for=\"nvsUpload\">Hier kann eine Backup-Datei importiert werden.</label>\
<input type=\"file\" class=\"form-control-file\" id=\"nvsUpload\" name=\"nvsUpload\" accept=\".txt\">\
</div>\
<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>';\
\
var socket = new WebSocket(\"ws://%IPv4%/ws\");\
\
function connect() {\
socket = new WebSocket(\"ws://%IPv4%/ws\");\
}\
</div>\
<button type=\"submit\" class=\"btn btn-primary\">Absenden</button>\
</form>\
<div class=\"messages col-md-6 my-2\"></div>\
</div>\
<script type=\"text/javascript\">\
var DEBUG = false;\
var lastIdclicked = '';\
var host = $(location).attr('hostname');\
\
function ping() {\
var myObj = {\
\"ping\": {\
ping: 'ping'\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
tm = setTimeout(function () {\
alert(\"Die Verbindung zum Tonuino ist unterbrochen!\\nBitte Seite neu laden.\");\
}, 5000);\
}\
if (DEBUG) {\
host = \"192.168.178.112\";\
}\
\
function pong() {\
clearTimeout(tm);\
}\
toastr.options = {\
\"closeButton\": false,\
\"debug\": false,\
\"newestOnTop\": false,\
\"progressBar\": false,\
\"positionClass\": \"toast-top-right\",\
\"preventDuplicates\": false,\
\"onclick\": null,\
\"showDuration\": \"300\",\
\"hideDuration\": \"1000\",\
\"timeOut\": \"5000\",\
\"extendedTimeOut\": \"1000\",\
\"showEasing\": \"swing\",\
\"hideEasing\": \"linear\",\
\"showMethod\": \"fadeIn\",\
\"hideMethod\": \"fadeOut\"\
};\
\
function postRendering(event, data) {\
Object.keys(data.instance._model.data).forEach(function (key, index) {\
\
var cur = data.instance.get_node(data.instance._model.data[key]);\
var lastFolder = cur['id'].split('/').filter(function (el) {\
return el.trim().length > 0;\
}).pop();\
if ((/\\.(mp3|MP3|ogg|wav|WAV|OGG|wma|WMA|acc|ACC|flac|FLAC)$/i).test(lastFolder)) {\
data.instance.set_type(data.instance._model.data[key], 'audio');\
} else {\
if (data.instance._model.data[key]['type'] == \"file\") {\
data.instance.disable_node(data.instance._model.data[key]);\
}\
}\
data.instance.rename_node(data.instance._model.data[key], lastFolder);\
});\
}\
\
function renderFileTree() {\
\
var filesURI = \"/files\";\
if (DEBUG) {\
filesURI = \"http://\" + host + \"/files\";\
}\
$('#filetree').jstree({\
'core': {\
'check_callback': true,\
'data': {\
url: filesURI,\
error: function (XMLHttpRequest, textStatus, errorThrown) {\
$('#j1_loading').hide();\
$(\"#refreshAction\").hide();\
$('#filetree').html(\"<div class='clickForRefresh' ><i class='fas fa-sync fa-1x'><span id='#clickForRefresh' > Dateien suchen.</span></div>\");\
$('#filetree').on(\"click\", function () {\
refreshFileList();\
});\
toastr.error(\"Die Verzeichnis Liste konnte nicht geladen werden.\");\
}\
},\
\
},\
'types': {\
'folder': {\
'icon': \"fa fa-folder\"\
},\
'file': {\
'icon': \"fa fa-file\"\
},\
'audio': {\
'icon': \"fa fa-file-audio\"\
},\
'default': {\
'icon': \"fa fa-folder\"\
}\
},\
'plugins': [\"themes\", \"types\"]\
}).bind('loaded.jstree', function (event, data) {\
postRendering(event, data);\
if ((data.instance._model.data['#']['children'].length == 0)) {\
toastr.info(\"Der SD Karten Index muss erzeugt werden.\");\
}\
}).bind('refresh.jstree', function (event, data) {\
postRendering(event, data);\
});\
}\
\
$('#filetree').on('select_node.jstree', function (e, data) {\
$('input[name=fileOrUrl]').val(data.node.id);\
\
if (data.node.type == \"folder\") {\
$('.option-folder').show();\
$('.option-file').hide();\
$('#playMode option').removeAttr('selected').filter('[value=3]').attr('selected', true);\
}\
\
socket.onopen = function () {\
if (data.node.type == \"audio\") {\
$('.option-file').show();\
$('.option-folder').hide();\
$('#playMode option').removeAttr('selected').filter('[value=1]').attr('selected', true);\
}\
});\
\
$('#refreshAction').on(\"click\", function () {\
refreshFileList();\
$(\"#indexing-progress\").show();\
$(\"#refreshAction\").hide();\
});\
\
$('#playMode').on(\"change\", function () {\
if (this.value == 8) {\
$('#filebrowser').slideUp();\
} else {\
$('#filebrowser').slideDown();\
}\
});\
\
function showFileIndexingState(){\
$(\"#indexing-progress\").show();\
$(\"#refreshAction\").hide();\
}\
\
function hideFileIndexingState(){\
$(\"#indexing-progress\").hide();\
$(\"#refreshAction\").show();\
}\
\
var socket = undefined;\
var tm;\
\
function connect() {\
socket = new WebSocket(\"ws://\" + host + \"/ws\");\
\
socket.onopen = function () {\
setInterval(ping, 15000);\
};\
};\
\
socket.onclose = function(e) {\
socket.onclose = function (e) {\
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);\
setTimeout(function() {\
connect();\
socket = null;\
setTimeout(function () {\
connect();\
}, 5000);\
};\
};\
\
socket.onerror = function(err) {\
socket.onerror = function (err) {\
console.error('Socket encountered error: ', err.message, 'Closing socket');\
socket.close();\
};\
};\
\
socket.onmessage = function(event) {\
console.log(event.data);\
var socketMsg = JSON.parse(event.data);\
if (socketMsg.rfidId != null) {\
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);\
}\
} if (socketMsg.pong != null) {\
if (socketMsg.pong == 'pong') {\
toastr.info(\"RFID Tag mit \"+ socketMsg.rfidId + \" erkannt.\" );\
\
$(\"#rfidIdMusic\").effect(\"highlight\", {color:\"#abf5af\"}, 3000);\
$(\"#rfidIdMod\").effect(\"highlight\", {color:\"#abf5af\"}, 3000);\
\
} if (\"status\" in socketMsg) {\
if (socketMsg.status == \"ok\") {\
toastr.success(\"Aktion erfolgreich ausgeführt.\" );\
}\
} if (\"pong\" in socketMsg) {\
if (socketMsg.pong == 'pong') {\
pong();\
}\
}\
} if (\"refreshFileList\" in socketMsg){\
hideFileIndexingState();\
toastr.info(\"Die Datei Liste wurde neu erzeugt!\");\
$('#filetree').jstree(true).refresh();\
\
}\
if (\"indexingState\" in socketMsg){\
if(socketMsg.indexingState != null) {\
$(\"#currentProcessedFile\").text(socketMsg.indexingState);\
console.log(socketMsg.indexingState);\
}\
}\
};\
}\
\
function ping() {\
var myObj = {\
\"ping\": {\
ping: 'ping'\
}\
};\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
tm = setTimeout(function () {\
toastr.warning('Die Verbindung zum Tonuino ist unterbrochen! Bitte Seite neu laden.');\
}, 5000);\
}\
\
function genSettings(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"general\": {\
function pong() {\
clearTimeout(tm);\
}\
\
function refreshFileList(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"refreshFileList\": true\
};\
var myJSON = JSON.stringify(myObj);\
$(\"#refreshAction\").hide();\
socket.send(myJSON);\
showFileIndexingState();\
};\
\
function genSettings(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"general\": {\
iVol: document.getElementById('initialVolume').value,\
mVolSpeaker: document.getElementById('maxVolumeSpeaker').value,\
mVolHeadphone: document.getElementById('maxVolumeHeadphone').value,\
@ -292,89 +543,98 @@ static const char mgtWebsite[] PROGMEM = "<!DOCTYPE html>\
vIndLow: document.getElementById('voltageIndicatorLow').value,\
vIndHi: document.getElementById('voltageIndicatorHigh').value,\
vInt: document.getElementById('voltageCheckInterval').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function ftpSettings(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"ftp\": {\
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\": {\
};\
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,\
mqttUser: document.getElementById('mqttUser').value,\
mqttPwd: document.getElementById('mqttPwd').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function rfidMods(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"rfidMod\": {\
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 removeTrSlash(str) {\
if(str.substr(-1) === '/') {\
return str.substr(0, str.length - 1);\
}\
return str;\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function rfidAssign(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"rfidAssign\": {\
function removeTrSlash(str) {\
if (str.substr(-1) === '/') {\
return str.substr(0, str.length - 1);\
}\
return str;\
}\
\
function rfidAssign(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"rfidAssign\": {\
rfidIdMusic: document.getElementById('rfidIdMusic').value,\
fileOrUrl: removeTrSlash(document.getElementById('fileOrUrl').value),\
playMode: document.getElementById('playMode').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
function wifiConfig(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"wifiConfig\": {\
function wifiConfig(clickedId) {\
lastIdclicked = clickedId;\
var myObj = {\
\"wifiConfig\": {\
ssid: document.getElementById('ssid').value,\
pwd: document.getElementById('pwd').value,\
hostname: document.getElementById('hostname').value\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
</script>\
</body>\
}\
};\
var myJSON = JSON.stringify(myObj);\
socket.send(myJSON);\
}\
\
$(document).ready(function () {\
connect();\
renderFileTree();\
\
$(function () {\
$('[data-toggle=\"tooltip\"]').tooltip();\
});\
});\
</script>\
</body>\
</html>\
";
Loading…
Cancel
Save