Skip to content

Commit 9b2bee0

Browse files
fhirschmannclaude
andcommitted
feat: WebDAV server to mount the SD card as a network drive (disabled)
Adds a self-contained WebDAV/1,2 server (own FreeRTOS task on core 0, port 81) exposing the SD card read/write so a PC/Mac can copy audio over Finder/Explorer: OPTIONS/PROPFIND/GET+ranges/PUT/DELETE/MKCOL/MOVE/COPY/LOCK, optional HTTP-Basic auth, a settings sub-tab + control-tab toggle, command 188, MQTT `webdav` (plus a Home Assistant switch) and Bonjour (_webdav._tcp) discovery. Gated behind WEBDAV_ENABLE, currently OFF: the single-threaded server doesn't browse reliably under macOS Finder's persistent/parallel connections (needs proper HTTP keep-alive / concurrency). Kept in the tree for that follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9ebf5a9 commit 9b2bee0

16 files changed

Lines changed: 1160 additions & 19 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ neon logo that doubles as the SVG favicon ([`7be5254`](../../commit/7be5254)):
110110
| **Password protection**: single password (no username), 90-day session cookie, brute-force lockout, logout entry; off in hotspot mode. Scripts/API clients authenticate with the password as an **API key** (`X-API-Key` header / `apikey` query param) | [`e74e712`](../../commit/e74e712) |
111111
| **One-click OTA + version badge**: a Tools-tab button (also bindable command **186** / MQTT `firmware_update`) pulls the rolling `latest` GitHub release and flashes it over OTA; a navbar badge shows the running build and turns amber when an update is available (passive `/version` check) — click it to install | [`8527f5e`](../../commit/8527f5e) · [`b736abc`](../../commit/b736abc) |
112112
| **HTTP file sync**: pull audio files from a web server onto the SD card from a JSON manifest — additive, streamed in chunks straight to SD, background task with live progress + stop, abort-on-button, stall watchdog, auto-pauses playback and keeps the device awake mid-transfer. The manifest is streamed into the parser to halve peak RAM | [`ac24bbc`](../../commit/ac24bbc) · [`42d2c46`](../../commit/42d2c46) |
113+
| **WebDAV server**: mount the SD card as a network drive (`http://<ip>:81/`) to copy audio on/off it straight from Finder/Explorer — no FTP client needed. Self-contained server (OPTIONS/PROPFIND/GET/HEAD+ranges/PUT/DELETE/MKCOL/MOVE/COPY/LOCK) running in its own task pinned to core 0 so transfers never disturb the audio pipeline; optional HTTP-Basic credentials. Configure + auto-start on boot in its own **WebDAV** settings sub-tab; start/stop live from the Control tab, command **188** or MQTT `webdav` (Home Assistant switch included). With auto-start on, the share is announced over **Bonjour/mDNS** (`_webdav._tcp`, `path=/`) so it pops up by itself in the Finder/Explorer network sidebar — no manual "Connect to Server" needed | [`WEBDAV`](../../commits/master) |
113114
| **RFID-tag syncing (central server + peer-to-peer)**: keep tag assignments in sync across a PHP server *and* other ESPuinos — newest-wins by timestamp, deletions propagate via tombstones, automatic catch-up after coming online so a player used offline/outdoors keeps working. Run from the Tools tab, command **187** or MQTT `rfid_sync` | [`99ffb5b`](../../commit/99ffb5b) |
114115
| **Equalizer profiles**: Flat / Music / Audiobook-Speech / Deep-voices / Custom presets on the 3-band tone control (speech presets keep deep narrator voices intelligible); assignable per file/folder, cycle via command **154** / MQTT `equalizer`. An over-full per-path rule set is now rejected with a clear error instead of silently failing | [`11ade33`](../../commit/11ade33) · [`addff5c`](../../commit/addff5c) |
115116
| **Audiobook resume hardening**: the play-position is checkpointed to flash every ~30 s while playing, so a dead-battery / hard power-off only loses a few seconds instead of the whole track; the learned-cards list shows each tag's resume point with a restart-from-start button | [`a9f498f`](../../commit/a9f498f) |

‎html/locales/de.json‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"wifi": "WLAN",
3838
"mqtt": "MQTT",
3939
"ftp": "FTP",
40+
"webdav": "WebDAV",
4041
"sync": "Sync",
4142
"bluetooth": "Bluetooth",
4243
"general": "Allgemein",
@@ -196,6 +197,8 @@
196197
"nightOff": "Nacht Aus",
197198
"ftpOn": "FTP An",
198199
"ftpOff": "FTP Aus",
200+
"webdavOn": "WebDAV An",
201+
"webdavOff": "WebDAV Aus",
199202
"repeatOff": "Aus",
200203
"repeatPlaylist": "Playlist",
201204
"repeatTrack": "Titel",
@@ -229,6 +232,7 @@
229232
"sleep": "Sleep-Timer",
230233
"night": "Nachtmodus",
231234
"ftp": "FTP-Server",
235+
"webdav": "WebDAV-Server",
232236
"bluetooth": "Bluetooth-Modus",
233237
"lock": "Tasten sperren"
234238
}
@@ -454,6 +458,29 @@
454458
"stopButton": "FTP-Server stoppen"
455459
}
456460
},
461+
"webdav": {
462+
"title": "WebDAV-Einstellungen",
463+
"desc": "Bindet die SD-Karte als Netzlaufwerk ein, sodass du am PC/Mac Audiodateien direkt darauf kopieren kannst (im Finder/Explorer als WebDAV-Server verbinden).",
464+
"enable": "WebDAV beim Start automatisch aktivieren",
465+
"user": {
466+
"title": "WebDAV-Benutzername",
467+
"placeholder": "Benutzername"
468+
},
469+
"pwd": {
470+
"title": "WebDAV-Passwort",
471+
"placeholder": "Passwort",
472+
"show": "Passwort anzeigen/verbergen"
473+
},
474+
"auth": {
475+
"hint": "Benutzername und Passwort leer lassen, um ohne Anmeldung zuzugreifen."
476+
},
477+
"start": {
478+
"title": "WebDAV-Server starten",
479+
"desc": "Aktiviert den WebDAV-Server bis zum Neustart des Geräts.",
480+
"button": "WebDAV-Server starten",
481+
"stopButton": "WebDAV-Server stoppen"
482+
}
483+
},
457484
"sync": {
458485
"title": "Datei-Synchronisation",
459486
"intro": "Lädt Dateien von einem Webserver (z.B. nginx) auf die SD-Karte. Der ESPuino holt ein <b>JSON-Manifest</b> von der angegebenen URL und lädt alle darin gelisteten Dateien, die lokal fehlen oder eine andere Größe haben. Es wird <b>nur hinzugefügt</b> – lokale Dateien werden nie gelöscht. Die Synchronisation läuft nur, wenn gerade nichts von der SD-Karte abgespielt wird.",

‎html/locales/en.json‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"wifi": "WiFi",
3838
"mqtt": "MQTT",
3939
"ftp": "FTP",
40+
"webdav": "WebDAV",
4041
"sync": "Sync",
4142
"bluetooth": "Bluetooth",
4243
"general": "General",
@@ -196,6 +197,8 @@
196197
"nightOff": "Night Off",
197198
"ftpOn": "FTP On",
198199
"ftpOff": "FTP Off",
200+
"webdavOn": "WebDAV On",
201+
"webdavOff": "WebDAV Off",
199202
"repeatOff": "Off",
200203
"repeatPlaylist": "Playlist",
201204
"repeatTrack": "Track",
@@ -229,6 +232,7 @@
229232
"sleep": "Sleep timer",
230233
"night": "Night mode",
231234
"ftp": "FTP server",
235+
"webdav": "WebDAV server",
232236
"bluetooth": "Bluetooth mode",
233237
"lock": "Lock controls"
234238
}
@@ -454,6 +458,29 @@
454458
"stopButton": "Stop FTP server"
455459
}
456460
},
461+
"webdav": {
462+
"title": "WebDAV settings",
463+
"desc": "Mounts the SD card as a network drive so you can copy audio files onto it straight from your PC/Mac (connect to it as a WebDAV server in Finder/Explorer).",
464+
"enable": "Start WebDAV automatically on boot",
465+
"user": {
466+
"title": "WebDAV username",
467+
"placeholder": "username"
468+
},
469+
"pwd": {
470+
"title": "WebDAV password",
471+
"placeholder": "password",
472+
"show": "Show/hide password"
473+
},
474+
"auth": {
475+
"hint": "Leave username and password empty to allow access without a login."
476+
},
477+
"start": {
478+
"title": "Start WebDAV server",
479+
"desc": "Enables the WebDAV server until the device is restarted.",
480+
"button": "Start WebDAV server",
481+
"stopButton": "Stop WebDAV server"
482+
}
483+
},
457484
"sync": {
458485
"title": "File synchronization",
459486
"intro": "Downloads files from a web server (e.g. nginx) onto the SD card. The ESPuino fetches a <b>JSON manifest</b> from the URL below and downloads every file listed in it that is missing locally or has a different size. It is <b>additive only</b> – local files are never deleted. Syncing only runs while nothing is being played from the SD card.",

‎html/locales/fr.json‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"wifi": "WiFi",
3838
"mqtt": "MQTT",
3939
"ftp": "FTP",
40+
"webdav": "WebDAV",
4041
"sync": "Sync",
4142
"bluetooth": "Bluetooth",
4243
"general": "Général",
@@ -189,6 +190,8 @@
189190
"nightOff": "Nuit Off",
190191
"ftpOn": "FTP On",
191192
"ftpOff": "FTP Off",
193+
"webdavOn": "WebDAV On",
194+
"webdavOff": "WebDAV Off",
192195
"repeatOff": "Off",
193196
"repeatPlaylist": "Playlist",
194197
"repeatTrack": "Piste",
@@ -222,6 +225,7 @@
222225
"sleep": "Minuterie de veille",
223226
"night": "Mode nuit",
224227
"ftp": "Serveur FTP",
228+
"webdav": "Serveur WebDAV",
225229
"bluetooth": "Mode Bluetooth",
226230
"lock": "Verrouiller"
227231
}
@@ -447,6 +451,29 @@
447451
"stopButton": "Arrêter le serveur FTP"
448452
}
449453
},
454+
"webdav": {
455+
"title": "Paramètres WebDAV",
456+
"desc": "Monte la carte SD comme lecteur réseau pour copier des fichiers audio directement depuis votre PC/Mac (connectez-vous en tant que serveur WebDAV dans le Finder/Explorateur).",
457+
"enable": "Démarrer WebDAV automatiquement au démarrage",
458+
"user": {
459+
"title": "Nom d'utilisateur WebDAV",
460+
"placeholder": "nom d'utilisateur"
461+
},
462+
"pwd": {
463+
"title": "Mot de passe WebDAV",
464+
"placeholder": "mot de passe",
465+
"show": "Afficher/masquer le mot de passe"
466+
},
467+
"auth": {
468+
"hint": "Laissez le nom d'utilisateur et le mot de passe vides pour un accès sans connexion."
469+
},
470+
"start": {
471+
"title": "Démarrer serveur WebDAV",
472+
"desc": "Active le serveur WebDAV jusqu'au redémarrage du périphérique.",
473+
"button": "Démarrer le serveur WebDAV",
474+
"stopButton": "Arrêter le serveur WebDAV"
475+
}
476+
},
450477
"sync": {
451478
"title": "Synchronisation de fichiers",
452479
"intro": "Télécharge des fichiers depuis un serveur web (p.ex. nginx) vers la carte SD. L'ESPuino récupère un <b>manifeste JSON</b> à l'URL ci-dessous et télécharge tous les fichiers listés qui manquent localement ou ont une taille différente. C'est <b>uniquement additif</b> – aucun fichier local n'est supprimé. La synchronisation ne s'exécute que lorsqu'aucune lecture n'est en cours depuis la carte SD.",

‎html/management.html‎

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,6 +2182,11 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
21822182
<span class="fas fa-server"></span>
21832183
<span id="lbl-ftp" class="d-none d-sm-inline" data-i18n="control.ftpOff">FTP Off</span>
21842184
</button>
2185+
<!-- WebDAV Server Button (revealed only when the firmware reports WebDAV support) -->
2186+
<button type="button" class="btn btn-sm cp-control-button" id="btn-webdav" style="display:none" onclick="toggleWebdav()" data-i18n="[aria-label]control.a11y.webdav">
2187+
<span class="fas fa-hdd"></span>
2188+
<span id="lbl-webdav" class="d-none d-sm-inline" data-i18n="control.webdavOff">WebDAV Off</span>
2189+
</button>
21852190
<!-- Bluetooth Mode Dropdown -->
21862191
<div class="dropdown d-inline-flex align-items-center">
21872192
<button type="button" class="btn btn-sm cp-control-button dropdown-toggle" id="btn-bt-mode" data-bs-toggle="dropdown" aria-expanded="false" data-i18n="[aria-label]control.a11y.bluetooth">
@@ -2444,6 +2449,7 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
24442449
<a class="nav-item nav-link" id="nav-wifi-tab" data-bs-toggle="tab" data-bs-target="#nav-wifi" href="#nav-wifi" role="tab" aria-controls="nav-wifi"><i class="fas fa-wifi"></i> <span class="d-none d-md-inline" data-i18n="nav.wifi"></span></a>
24452450
<a class="nav-item nav-link" id="nav-mqtt-tab" data-visible="false" data-bs-toggle="tab" data-bs-target="#nav-mqtt" href="#nav-mqtt" role="tab" aria-controls="nav-mqtt"><i class="fas fa-network-wired"></i> <span class="d-none d-md-inline" data-i18n="nav.mqtt"></span></a>
24462451
<a class="nav-item nav-link" id="nav-ftp-tab" data-visible="false" data-bs-toggle="tab" data-bs-target="#nav-ftp" href="#nav-ftp" role="tab" aria-controls="nav-ftp"><i class="fas fa-folder"></i> <span class="d-none d-md-inline" data-i18n="nav.ftp"></span></a>
2452+
<a class="nav-item nav-link" id="nav-webdav-tab" data-visible="false" data-bs-toggle="tab" data-bs-target="#nav-webdav" href="#nav-webdav" role="tab" aria-controls="nav-webdav"><i class="fas fa-hdd"></i> <span class="d-none d-md-inline" data-i18n="nav.webdav"></span></a>
24472453
<a class="nav-item nav-link" id="nav-sync-tab" data-bs-toggle="tab" data-bs-target="#nav-sync" href="#nav-sync" role="tab" aria-controls="nav-sync"><i class="fas fa-sync"></i> <span class="d-none d-md-inline" data-i18n="nav.sync"></span></a>
24482454
<a class="nav-item nav-link" id="nav-bt-tab" data-visible="false" data-bs-toggle="tab" data-bs-target="#nav-bt" href="#nav-bt" role="tab" aria-controls="nav-bt"><i class="fab fa-bluetooth"></i> <span class="d-none d-md-inline" data-i18n="nav.bluetooth"></span></a>
24492455
<a class="nav-item nav-link" id="nav-homekit-tab" data-visible="false" data-bs-toggle="tab" data-bs-target="#nav-homekit" href="#nav-homekit" role="tab" aria-controls="nav-homekit"><i class="fab fa-apple"></i> <span class="d-none d-md-inline">HomeKit</span></a>
@@ -3247,6 +3253,50 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
32473253
</div>
32483254
</div>
32493255
</div>
3256+
<div class="tab-pane fade" id="nav-webdav" role="tabpanel" aria-labelledby="nav-webdav-tab">
3257+
<div class="container" id="webdavConfig">
3258+
<form action="#webdavConfig" method="POST" onsubmit="webdavSettings('webdavConfig'); return false">
3259+
<div class="mb-3 col-md-12">
3260+
<legend data-i18n="webdav.title"></legend>
3261+
<div class="mb-2" data-i18n="webdav.desc"></div>
3262+
<div class="mb-3"><code id="webdavMountUrl">http://&lt;ip&gt;:81/</code></div>
3263+
<div class="form-check form-switch mb-3">
3264+
<input class="form-check-input" type="checkbox" id="webdavEnable" name="webdavEnable">
3265+
<label class="form-check-label" for="webdavEnable" data-i18n="webdav.enable"></label>
3266+
</div>
3267+
<label for="webdavUser" data-i18n="[prepend]webdav.user.title">:</label>
3268+
<input type="text" class="form-control" id="webdavUser" maxlength="0"
3269+
data-i18n="[placeholder]webdav.user.placeholder" name="webdavUser" value="">
3270+
<label for="webdavPwd" data-i18n="[prepend]webdav.pwd.title">:</label>
3271+
<div class="input-group">
3272+
<input type="password" class="form-control" id="webdavPwd" maxlength="0"
3273+
data-i18n="[placeholder]webdav.pwd.placeholder" name="webdavPwd" value="">
3274+
<button class="btn cp-reveal-btn" type="button" id="webdavPwdToggle" tabindex="-1"
3275+
data-i18n="[title]webdav.pwd.show" onclick="togglePasswordField('webdavPwd', 'webdavPwdToggle')">
3276+
<i class="fas fa-eye"></i>
3277+
</button>
3278+
</div>
3279+
<small class="text-muted" data-i18n="webdav.auth.hint"></small>
3280+
</div>
3281+
<div class="text-center">
3282+
<button type="reset" class="btn btn-warning" data-i18n="reset"></button>&nbsp;<button
3283+
type="submit" class="btn btn-primary" data-i18n="submit"></button>
3284+
</div>
3285+
</form>
3286+
<hr>
3287+
</div>
3288+
<div class="container" id="webdavStart">
3289+
<div class="col-md-12">
3290+
<legend data-i18n="webdav.start.title"></legend>
3291+
<div data-i18n="webdav.start.desc"></div>
3292+
<br>
3293+
<div class="text-center">
3294+
<button type="button" class="btn btn-primary" id="webdavModeBtn" onclick="webdavSetStatus(1)"
3295+
data-i18n="webdav.start.button"></button>
3296+
</div>
3297+
</div>
3298+
</div>
3299+
</div>
32503300
<div class="tab-pane fade" id="nav-sync" role="tabpanel" aria-labelledby="nav-sync-tab">
32513301
<div class="container" id="syncConfig">
32523302
<!-- ===== Section 1: file synchronization ===== -->
@@ -4940,6 +4990,9 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
49404990
if ("ftpStatus" in socketMsg) {
49414991
updateFtpButton(socketMsg.ftpStatus.running);
49424992
}
4993+
if ("webdavStatus" in socketMsg) {
4994+
updateWebdavButton(socketMsg.webdavStatus.running);
4995+
}
49434996
if ("battery" in socketMsg) {
49444997
updateBatteryIndicator(socketMsg.battery);
49454998
}
@@ -5306,6 +5359,20 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
53065359
document.getElementById("ftpPwd").value = (ftpSettings.password === "-1") ? "" : ftpSettings.password;
53075360
document.getElementById("ftpPwd").setAttribute('maxlength', ftpSettings.maxPwdLength);
53085361
}
5362+
// webdav (the sub-tab stays hidden unless the firmware reports WebDAV support)
5363+
let webdavSettingsData = settings.webdav;
5364+
if (webdavSettingsData) {
5365+
document.getElementById('nav-webdav-tab').setAttribute('data-visible', true);
5366+
document.getElementById('btn-webdav').style.display = '';
5367+
document.getElementById("webdavUser").value = (webdavSettingsData.username === "-1") ? "" : webdavSettingsData.username;
5368+
document.getElementById("webdavUser").setAttribute('maxlength', webdavSettingsData.maxUserLength);
5369+
document.getElementById("webdavPwd").value = (webdavSettingsData.password === "-1") ? "" : webdavSettingsData.password;
5370+
document.getElementById("webdavPwd").setAttribute('maxlength', webdavSettingsData.maxPwdLength);
5371+
document.getElementById("webdavEnable").checked = !!webdavSettingsData.enable;
5372+
const port = webdavSettingsData.port || 81;
5373+
document.getElementById("webdavMountUrl").textContent = "http://" + host + ":" + port + "/";
5374+
updateWebdavButton(!!webdavSettingsData.running);
5375+
}
53095376
// http file sync
53105377
let syncSettingsData = settings.sync;
53115378
if (syncSettingsData) {
@@ -5933,6 +6000,33 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
59336000
socket.send(myJSON);
59346001
}
59356002

6003+
/* save the WebDAV credentials + auto-start-on-boot flag */
6004+
async function webdavSettings(clickedId) {
6005+
lastIdclicked = clickedId;
6006+
var myObj = {
6007+
"webdav": {
6008+
username: document.getElementById('webdavUser').value,
6009+
password: document.getElementById('webdavPwd').value,
6010+
enable: document.getElementById('webdavEnable').checked
6011+
}
6012+
};
6013+
await fetch("http://" + host + "/settings", {
6014+
method: "POST",
6015+
headers: { "Content-Type": "application/json" },
6016+
body: JSON.stringify(myObj)
6017+
});
6018+
toaster.info(i18next.t("toast.saved"));
6019+
}
6020+
6021+
function webdavSetStatus(start) {
6022+
var myObj = {
6023+
"webdavStatus": {
6024+
start: start
6025+
}
6026+
};
6027+
socket.send(JSON.stringify(myObj));
6028+
}
6029+
59366030
/* save the HTTP file-sync configuration (manifest URL + optional Basic Auth) */
59376031
async function syncSettings() {
59386032
var myObj = {
@@ -6176,6 +6270,36 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
61766270
ftpSetStatus(b && b.classList.contains('active-green') ? 0 : 1);
61776271
}
61786272

6273+
/* mirror the WebDAV running state onto the start/stop button, the sub-tab and the control-tab button */
6274+
function updateWebdavButton(running) {
6275+
const btn = document.getElementById('webdavModeBtn');
6276+
if (btn) {
6277+
btn.setAttribute('data-i18n', running ? 'webdav.start.stopButton' : 'webdav.start.button');
6278+
btn.onclick = () => webdavSetStatus(running ? 0 : 1);
6279+
btn.classList.toggle('btn-danger', running);
6280+
btn.classList.toggle('btn-primary', !running);
6281+
if (localize) {
6282+
localize('#webdavModeBtn');
6283+
}
6284+
}
6285+
const tab = document.getElementById('nav-webdav-tab');
6286+
if (tab) {
6287+
tab.classList.toggle('ftp-active', running);
6288+
}
6289+
const cBtn = document.getElementById('btn-webdav');
6290+
const cLbl = document.getElementById('lbl-webdav');
6291+
if (cBtn && cLbl) {
6292+
cBtn.className = running ? "btn btn-sm cp-control-button active-green" : "btn btn-sm cp-control-button";
6293+
cLbl.setAttribute('data-i18n', running ? 'control.webdavOn' : 'control.webdavOff');
6294+
cLbl.innerHTML = i18next.t(running ? 'control.webdavOn' : 'control.webdavOff', running ? 'WebDAV On' : 'WebDAV Off');
6295+
}
6296+
}
6297+
6298+
function toggleWebdav() {
6299+
const b = document.getElementById('btn-webdav');
6300+
webdavSetStatus(b && b.classList.contains('active-green') ? 0 : 1);
6301+
}
6302+
61796303
function updateLockControlsState(locked) {
61806304
const btn = document.getElementById('btn-lock-controls');
61816305
const ico = document.getElementById('ico-lock-controls');

‎src/Cmd.cpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "Sync.h"
1818
#include "System.h"
1919
#include "Web.h"
20+
#include "Webdav.h"
2021
#include "Wlan.h"
2122

2223
static void Cmd_HandleSleepAction(bool enable, const char *enLogMsg, const char *enMqttMsg) {
@@ -439,6 +440,15 @@ void Cmd_Action(const uint16_t mod) {
439440
break;
440441
}
441442

443+
case CMD_TOGGLE_WEBDAV_SERVER: {
444+
if (Webdav_IsServerRunning()) {
445+
Webdav_DisableServer();
446+
} else {
447+
Webdav_EnableServer();
448+
}
449+
break;
450+
}
451+
442452
case CMD_SEEK_FORWARDS: {
443453
gPlayProperties.seekmode = SEEK_FORWARDS;
444454
break;

0 commit comments

Comments
 (0)