Skip to content

Commit 7d86c3d

Browse files
fhirschmannclaude
andcommitted
feat(oled): make the OLED display web-configurable
Add a new OLED settings tab so the 128x64 SH1106/SSD1306 display can be configured at runtime (persisted in NVS, applied live without a reboot): - enable/disable the panel (powers it off when disabled) - pick the startup/attract animation: none, boot terminal, login splash or full (boot + login) - set the two idle/boot header text lines - toggle the playing-screen fields: battery %, time, WiFi marker and the volume overlay - flip the panel by 180 degrees for upside-down mounting Display.cpp now caches the config from NVS (Display_LoadConfig) and exposes Display_ReloadConfig() to apply changes live, plus Display_Toggle() for the new CMD_TOGGLE_OLED command (bindable to buttons, e.g. to blank the screen at night). Settings are wired through settingsToJSON/defaults (WebSettings.cpp) and JSONToSettings (Web.cpp). Web UI gets the OLED subtab (fillSettings/genSettings/sections) with de/en/fr translations; the GitHub-Pages demo mock and README are updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e1be7d4 commit 7d86c3d

12 files changed

Lines changed: 305 additions & 26 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ neon logo that doubles as the SVG favicon ([`7be5254`](../../commit/7be5254)):
239239
| Unlocking controls via button press while locked | [`d83e15f`](../../commit/d83e15f) |
240240
| Support for a 6th button | [`b116151`](../../commit/b116151) |
241241
| OLED display support (SH1106/SSD1306 128×64 over I2C): boot splash, idle screen with IP + READY, now-playing title (up to 3 lines, scrolling) with battery/time/wifi status bar, and a volume bar | [`8ce8104`](../../commit/8ce8104) |
242+
| Web-configurable OLED (new **OLED** settings tab): enable/disable the display, pick the startup animation (none / boot terminal / login splash / full), set the idle header text, toggle the battery/time/wifi/volume fields, and flip the panel 180°. Also a `CMD_TOGGLE_OLED` command for keybindings to blank the screen (e.g. at night) | [`79f928d`](../../commit/79f928d) |
242243
| Audiobook resume fade-in: continuing a saved position briefly stutters in the first ~2 s (file open + MP3 header decode + seek-flush saturate the 1-bit SD/CPU while I2S already plays). The resume now rewinds a few seconds and fades the volume up over that span, so the glitch lands on already-heard audio — no content lost, clean sound from where you stopped. Tunable / disable via `RESUME_FADEIN_DURATION_MS` and `RESUME_FADEIN_REWIND_S` in `settings.h` | [`583225f`](../../commit/583225f) |
243244

244245
### Virtual RFID cards

‎html/locales/de.json‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"help": "Hilfe",
4848
"rfidcards": "RFID",
4949
"leds": "LEDs",
50+
"oled": "OLED",
5051
"power": "Power",
5152
"security": "Sicherheit"
5253
},
@@ -82,6 +83,26 @@
8283
"reverserotation": "Drehrichtung umkehren",
8384
"offset": "Offset Start-LED"
8485
},
86+
"oled": {
87+
"title": "OLED-Display",
88+
"enable": "OLED-Display aktivieren",
89+
"enablehint": "Deaktiviert schaltet das Display ab (z. B. für die Nacht).",
90+
"startanim": "Startanimation",
91+
"anim": {
92+
"none": "Keine (direkt zum Leerlauf)",
93+
"boot": "Boot-Terminal",
94+
"login": "Login-Sequenz",
95+
"full": "Komplett (Boot + Login)"
96+
},
97+
"idleline1": "Leerlauf-Text Zeile 1",
98+
"idleline2": "Leerlauf-Text Zeile 2",
99+
"showfields": "Anzeige während der Wiedergabe",
100+
"showbattery": "Akkustand anzeigen",
101+
"showtime": "Spielzeit anzeigen",
102+
"showwifi": "WLAN-Status anzeigen",
103+
"showvolume": "Lautstärke-Overlay anzeigen",
104+
"flip": "Anzeige um 180° drehen"
105+
},
85106
"buttons": {
86107
"title": "Taster-Zuordnungen",
87108
"short": "kurz",
@@ -364,6 +385,7 @@
364385
"152": "Uhrzeit ansagen",
365386
"153": "Ambient Light umschalten",
366387
"154": "Equalizer-Profil durchschalten",
388+
"156": "OLED-Display an/aus",
367389
"186": "Firmware aktualisieren (GitHub)",
368390
"0": "Lösche Zuordnung",
369391
"170": "Play/Pause",

‎html/locales/en.json‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"help": "Help",
4848
"rfidcards": "RFID",
4949
"leds": "LEDs",
50+
"oled": "OLED",
5051
"power": "Power",
5152
"security": "Security"
5253
},
@@ -82,6 +83,26 @@
8283
"reverserotation": "Reverse direction of rotation",
8384
"offset": "Offset start LED"
8485
},
86+
"oled": {
87+
"title": "OLED display",
88+
"enable": "Enable OLED display",
89+
"enablehint": "When disabled the panel is powered off (e.g. for night-time).",
90+
"startanim": "Startup animation",
91+
"anim": {
92+
"none": "None (go straight to idle)",
93+
"boot": "Boot terminal",
94+
"login": "Login sequence",
95+
"full": "Full (boot + login)"
96+
},
97+
"idleline1": "Idle text line 1",
98+
"idleline2": "Idle text line 2",
99+
"showfields": "Show while playing",
100+
"showbattery": "Show battery level",
101+
"showtime": "Show playback time",
102+
"showwifi": "Show WiFi status",
103+
"showvolume": "Show volume overlay",
104+
"flip": "Rotate display by 180°"
105+
},
85106
"buttons": {
86107
"title": "Button assignments",
87108
"short": "short press",
@@ -364,6 +385,7 @@
364385
"152": "Announce current time",
365386
"153": "Toggle Ambient Light",
366387
"154": "Cycle equalizer profile",
388+
"156": "Toggle OLED display",
367389
"186": "Update firmware (GitHub)",
368390
"0": "Remove assignment",
369391
"170": "Toggle Play/Pause",

‎html/locales/fr.json‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"help": "Aide",
4848
"rfidcards": "RFID",
4949
"leds": "LEDs",
50+
"oled": "OLED",
5051
"power": "Alim.",
5152
"security": "Sécurité"
5253
},
@@ -82,6 +83,26 @@
8283
"reverserotation": "Inversion du sens de rotation",
8384
"offset": "Début de l'offset LED"
8485
},
86+
"oled": {
87+
"title": "Écran OLED",
88+
"enable": "Activer l'écran OLED",
89+
"enablehint": "Désactivé, l'écran est éteint (par ex. la nuit).",
90+
"startanim": "Animation de démarrage",
91+
"anim": {
92+
"none": "Aucune (directement au repos)",
93+
"boot": "Terminal de démarrage",
94+
"login": "Séquence de connexion",
95+
"full": "Complète (démarrage + connexion)"
96+
},
97+
"idleline1": "Texte au repos ligne 1",
98+
"idleline2": "Texte au repos ligne 2",
99+
"showfields": "Afficher pendant la lecture",
100+
"showbattery": "Afficher le niveau de batterie",
101+
"showtime": "Afficher le temps de lecture",
102+
"showwifi": "Afficher l'état du WiFi",
103+
"showvolume": "Afficher l'overlay de volume",
104+
"flip": "Pivoter l'écran de 180°"
105+
},
85106
"buttons": {
86107
"title": "Attribution des boutons",
87108
"short": "presse courte",
@@ -358,6 +379,7 @@
358379
"0": "Supprimer l'affectation",
359380
"153": "Basculement de la lumière ambiante",
360381
"154": "Changer de profil d'égaliseur",
382+
"156": "Activer/désactiver l'écran OLED",
361383
"186": "Mettre à jour le firmware (GitHub)",
362384
"170": "Basculer Lecture/Pause",
363385
"171": "Piste précédente",

‎html/management.html‎

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2478,6 +2478,7 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
24782478
<a class="nav-item nav-link" id="settings-buttons-subtab" data-bs-toggle="tab" data-bs-target="#settings-buttons" href="#settings-buttons" role="tab" aria-controls="settings-buttons"><i class="fas fa-keyboard"></i> <span class="d-none d-md-inline" data-i18n="nav.buttons"></span></a>
24792479
<a class="nav-item nav-link" id="settings-rfid-subtab" data-bs-toggle="tab" data-bs-target="#settings-rfid" href="#settings-rfid" role="tab" aria-controls="settings-rfid"><i class="fas fa-id-card"></i> <span class="d-none d-md-inline" data-i18n="nav.rfidcards"></span></a>
24802480
<a class="nav-item nav-link" id="settings-leds-subtab" data-bs-toggle="tab" data-bs-target="#settings-leds" href="#settings-leds" role="tab" aria-controls="settings-leds"><i class="fas fa-lightbulb"></i> <span class="d-none d-md-inline" data-i18n="nav.leds"></span></a>
2481+
<a class="nav-item nav-link" id="settings-oled-subtab" data-visible="false" data-bs-toggle="tab" data-bs-target="#settings-oled" href="#settings-oled" role="tab" aria-controls="settings-oled"><i class="fas fa-tv"></i> <span class="d-none d-md-inline" data-i18n="nav.oled"></span></a>
24812482
<a class="nav-item nav-link" id="settings-power-subtab" data-bs-toggle="tab" data-bs-target="#settings-power" href="#settings-power" role="tab" aria-controls="settings-power"><i class="fas fa-bolt"></i> <span class="d-none d-md-inline" data-i18n="nav.power"></span></a>
24822483
<a class="nav-item nav-link" id="settings-security-subtab" data-bs-toggle="tab" data-bs-target="#settings-security" href="#settings-security" role="tab" aria-controls="settings-security"><i class="fas fa-lock"></i> <span class="d-none d-md-inline" data-i18n="nav.security"></span></a>
24832484
<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>
@@ -3046,6 +3047,40 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
30463047
</div>
30473048
</div>
30483049
</div>
3050+
<div class="tab-pane fade" id="settings-oled" role="tabpanel" aria-labelledby="settings-oled-subtab">
3051+
<div class="container">
3052+
<div class="col-md-12 mb-4">
3053+
<fieldset>
3054+
<legend class="w-auto" data-i18n="settingsex.oled.title"></legend><br>
3055+
<input type="checkbox" id="oled_enable" name="oled_enable" value="true">&nbsp;
3056+
<label for="oled_enable" data-i18n="settingsex.oled.enable"></label><br>
3057+
<small class="text-muted" data-i18n="settingsex.oled.enablehint"></small><br><br>
3058+
<label for="oled_startAnim" data-i18n="settingsex.oled.startanim"></label>: <br>
3059+
<select class="form-control form-select" id="oled_startAnim" name="oled_startAnim">
3060+
<option value="0" data-i18n="settingsex.oled.anim.none"></option>
3061+
<option value="1" data-i18n="settingsex.oled.anim.boot"></option>
3062+
<option value="2" data-i18n="settingsex.oled.anim.login"></option>
3063+
<option value="3" data-i18n="settingsex.oled.anim.full"></option>
3064+
</select><br>
3065+
<label for="oled_idleLine1" data-i18n="settingsex.oled.idleline1"></label>: <br>
3066+
<input type="text" class="form-control" id="oled_idleLine1" name="oled_idleLine1" maxlength="21"><br>
3067+
<label for="oled_idleLine2" data-i18n="settingsex.oled.idleline2"></label>: <br>
3068+
<input type="text" class="form-control" id="oled_idleLine2" name="oled_idleLine2" maxlength="21"><br>
3069+
<span data-i18n="settingsex.oled.showfields"></span>:<br>
3070+
<input type="checkbox" id="oled_showBattery" name="oled_showBattery" value="true">&nbsp;
3071+
<label for="oled_showBattery" data-i18n="settingsex.oled.showbattery"></label><br>
3072+
<input type="checkbox" id="oled_showTime" name="oled_showTime" value="true">&nbsp;
3073+
<label for="oled_showTime" data-i18n="settingsex.oled.showtime"></label><br>
3074+
<input type="checkbox" id="oled_showWifi" name="oled_showWifi" value="true">&nbsp;
3075+
<label for="oled_showWifi" data-i18n="settingsex.oled.showwifi"></label><br>
3076+
<input type="checkbox" id="oled_showVolume" name="oled_showVolume" value="true">&nbsp;
3077+
<label for="oled_showVolume" data-i18n="settingsex.oled.showvolume"></label><br><br>
3078+
<input type="checkbox" id="oled_flip" name="oled_flip" value="false">&nbsp;
3079+
<label for="oled_flip" data-i18n="settingsex.oled.flip"></label><br>
3080+
</fieldset>
3081+
</div>
3082+
</div>
3083+
</div>
30493084
<div class="tab-pane fade" id="settings-power" role="tabpanel" aria-labelledby="settings-power-subtab">
30503085
<div class="container">
30513086
<div class="col-md-12 mb-4">
@@ -5373,6 +5408,20 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
53735408
$('#led_reverseRotation').prop('checked', ledSettings.reverseRot);
53745409
document.getElementById('led_offsetStart').value = ledSettings.offsetStart;
53755410
}
5411+
// OLED display
5412+
let oledSettings = settings.oled;
5413+
if (oledSettings) {
5414+
document.getElementById('settings-oled-subtab').setAttribute('data-visible', true);
5415+
document.getElementById('oled_enable').checked = oledSettings.enable !== false;
5416+
document.getElementById('oled_startAnim').value = oledSettings.startAnim != null ? oledSettings.startAnim : 3;
5417+
document.getElementById('oled_idleLine1').value = oledSettings.idleLine1 != null ? oledSettings.idleLine1 : '';
5418+
document.getElementById('oled_idleLine2').value = oledSettings.idleLine2 != null ? oledSettings.idleLine2 : '';
5419+
document.getElementById('oled_showBattery').checked = oledSettings.showBattery !== false;
5420+
document.getElementById('oled_showTime').checked = oledSettings.showTime !== false;
5421+
document.getElementById('oled_showWifi').checked = oledSettings.showWifi !== false;
5422+
document.getElementById('oled_showVolume').checked = oledSettings.showVolume !== false;
5423+
document.getElementById('oled_flip').checked = oledSettings.flip === true;
5424+
}
53765425
// playlist
53775426
let playlistSettings = settings.playlist;
53785427
if (playlistSettings) {
@@ -5982,6 +6031,17 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
59826031
reverseRot: $('#led_reverseRotation').prop('checked'),
59836032
offsetStart: Number(document.getElementById("led_offsetStart").value),
59846033
},
6034+
"oled": {
6035+
enable: document.getElementById("oled_enable").checked,
6036+
startAnim: Number(document.getElementById("oled_startAnim").value),
6037+
idleLine1: document.getElementById("oled_idleLine1").value,
6038+
idleLine2: document.getElementById("oled_idleLine2").value,
6039+
showBattery: document.getElementById("oled_showBattery").checked,
6040+
showTime: document.getElementById("oled_showTime").checked,
6041+
showWifi: document.getElementById("oled_showWifi").checked,
6042+
showVolume: document.getElementById("oled_showVolume").checked,
6043+
flip: document.getElementById("oled_flip").checked,
6044+
},
59856045
"battery": {
59866046
warnLowVoltage: Number($('#warningLowVoltage').val()),
59876047
indicatorLow: Number($('#voltageIndicatorLow').val()),
@@ -6957,7 +7017,7 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
69577017
}
69587018
let errors = 0;
69597019
// the firmware applies one settings section per request
6960-
const sections = ['general', 'equalizer', 'led', 'battery', 'playlist', 'buttons', 'rotary', 'ftp', 'mqtt', 'bluetooth', 'sync'];
7020+
const sections = ['general', 'equalizer', 'led', 'oled', 'battery', 'playlist', 'buttons', 'rotary', 'ftp', 'mqtt', 'bluetooth', 'sync'];
69617021
for (const section of sections) {
69627022
if (backup.settings && backup.settings[section]) {
69637023
const response = await fetch("http://" + host + "/settings", {
@@ -8001,7 +8061,7 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
80018061
const cmdElem = document.createElement('select');
80028062
const cmdNothing = addOption(cmdElem, 0);
80038063
cmdNothing.setAttribute('data-i18n', "settingsex.buttons.noaction");
8004-
const cmds = [170, 171, 172, 173, 174, 184, 185, 175, 176, 177, 178, 179, 180, 181, 182, 183, 186, 199, 130, 140, 141, 142, 143, 144, 150, 151, 152, 153, 154, 120, 121, 100, 101, 102, 103, 104, 105, 106, 107, 110, 111, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250];
8064+
const cmds = [170, 171, 172, 173, 174, 184, 185, 175, 176, 177, 178, 179, 180, 181, 182, 183, 186, 199, 130, 140, 141, 142, 143, 144, 150, 151, 152, 153, 154, 156, 120, 121, 100, 101, 102, 103, 104, 105, 106, 107, 110, 111, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250];
80058065
for (const cmd of cmds) {
80068066
const opt = addOption(cmdElem, cmd);
80078067
if ([140, 141, 142, 143, 144].includes(cmd)) {
@@ -8015,7 +8075,7 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
80158075

80168076
/* The <select /> that houses all commands for the modification selectors */
80178077
const modElem = document.createElement('select');
8018-
const mods = [100, 179, 101, 102, 103, 104, 105, 106, 110, 111, 120, 121, 130, 140, 141, 142, 143, 144, 150, 151, 152, 153, 154, 0, 170, 171, 172, 173, 174, 184, 185, 180, 181, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250];
8078+
const mods = [100, 179, 101, 102, 103, 104, 105, 106, 110, 111, 120, 121, 130, 140, 141, 142, 143, 144, 150, 151, 152, 153, 154, 156, 0, 170, 171, 172, 173, 174, 184, 185, 180, 181, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250];
80198079
for (const mod of mods) {
80208080
const opt = addOption(modElem, mod);
80218081
if ([140, 141, 142, 143, 144].includes(mod)) {

‎src/Cmd.cpp‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "Backup.h"
88
#include "Battery.h"
99
#include "Bluetooth.h"
10+
#include "Display.h"
1011
#include "Ftp.h"
1112
#include "Led.h"
1213
#include "Log.h"
@@ -340,6 +341,16 @@ void Cmd_Action(const uint16_t mod) {
340341
break;
341342
}
342343

344+
case CMD_TOGGLE_OLED: {
345+
#ifdef OLED_ENABLE
346+
Display_Toggle();
347+
System_IndicateOk();
348+
#else
349+
System_IndicateError();
350+
#endif
351+
break;
352+
}
353+
343354
case CMD_PLAYPAUSE: {
344355
if ((OPMODE_NORMAL == System_GetOperationMode()) || (OPMODE_BLUETOOTH_SOURCE == System_GetOperationMode())) {
345356
AudioPlayer_SetTrackControl(PAUSEPLAY);

0 commit comments

Comments
 (0)