Skip to content

Add detailed and retained MQTT sleep timer status - #446

Open
TMHEINE wants to merge 19 commits into
biologist79:masterfrom
TMHEINE:local-sleeptimer-fix
Open

Add detailed and retained MQTT sleep timer status#446
TMHEINE wants to merge 19 commits into
biologist79:masterfrom
TMHEINE:local-sleeptimer-fix

Conversation

@TMHEINE

@TMHEINE TMHEINE commented Jul 25, 2026

Copy link
Copy Markdown

Summary

This adds detailed MQTT status topics for the sleep timer while keeping the existing sleepTimer topic backward compatible.

New retained MQTT topics

  • sleepTimerMode
  • sleepTimerActive
  • sleepTimerRemainingMinutes
  • sleepTimerRemainingTracks

Supported modes:

  • OFF
  • MINUTES
  • EOT
  • EOP
  • EO5T

Behaviour

The existing sleepTimer topic remains unchanged and continues to publish:

  • minute values
  • EOT
  • EOP
  • EO5T
  • 0

The new topics are retained and are republished after MQTT reconnect.

For minute-based timers, the remaining minutes are updated once per minute.

For EO5T, the remaining number of tracks is updated at track changes.

When a timer finishes or is cancelled, the retained state is reset to:

  • sleepTimerMode = OFF
  • sleepTimerActive = 0
  • sleepTimerRemainingMinutes = 0
  • sleepTimerRemainingTracks = 0

This happens before the device enters deep sleep.

Tested

  • minute timer including countdown
  • manual cancellation
  • EOT
  • EOP
  • EO5T
  • playlist repeat
  • MQTT reconnect
  • retained state after deep sleep

PlatformIO build:

lolin_d32_pro_sdmmc_pe  SUCCESS
RAM:   16.6%
Flash: 54.3%

biologist79 and others added 19 commits July 20, 2026 19:39
STATIC_IP_ENABLE, PLAY_LAST_RFID_AFTER_REBOOT, PAUSE_WHEN_RFID_REMOVED,
DONT_ACCEPT_SAME_RFID_TWICE, RESUME_ON_SAME_RFID and NEOPIXEL_REVERSE_ROTATION
only ever controlled the pre-first-save NVS default; every one of them has
long been fully configurable at runtime via the web interface. Collapsing
them to a single hardcoded false default removes the class of bug where the
compile-time default and the web-UI's read/factory-defaults code could drift
out of sync (as DONT_ACCEPT_SAME_RFID_TWICE's did).

ACCEPT_SAME_RFID_AFTER_TRACK_END, previously always coupled 1:1 to
PAUSE_WHEN_RFID_REMOVED with no independent toggle, is collapsed to its
permissive branch (allow reapplying the same RFID after track/playlist end).

Breaking for custom builds: a settings-override.h still defining one of
these macros will silently no longer have any effect. Static IP itself is
unaffected - it has been a fully runtime, per-network web-UI setting for a
while; the removed macro only fed a one-time migration for pre-multi-network
configs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SHUTDOWN_ON_BAT_CRITICAL only ever controlled the pre-first-save NVS
default; move it fully to the web interface with a new checkbox
("Bei kritischer Spannung automatisch abschalten"), off by default -
same behavior as before for anyone who didn't compile with the flag.

The threshold slider's minimum is raised from 2.0V to 2.8V (2.0V is
below any supported chemistry's safe cutoff), and its row is now
reachable/visible independent of any compile flag. Battery fieldset
also reordered (measurement interval moved to the top) and the unused
"Status über Neopixel anzeigen" description line removed.

Breaking for custom builds: a settings-override.h still defining
SHUTDOWN_ON_BAT_CRITICAL will silently no longer have any effect -
enable the checkbox in the web UI instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nothing in the codebase ever checked this macro via #ifdef - mono/stereo
playback has been a runtime web-UI setting (playMono in NVS) only. Just
dead documentation left over in settings.h/.sample and settings-complete.h.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Led_Task was still running its idle animation when esp_deep_sleep_start()
was called; NeoPixels just hold their last-sent color with no active
refresh, so the last-shown frame stayed lit forever. Call Led_Exit() first.

Deliberately not cutting peripheral power here (Power_PeripheralOff()):
on boards with a port-expander, that write immediately re-triggers the
wakeup-button's shared interrupt line, waking the board right back up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RFID_SCAN_INTERVAL was a compile-time-only constant; move it to the web
interface ("MFRC522 Scan-Intervall") with the same 100ms default, cached
once in RfidMfrc522_Init() rather than read from NVS on every task-loop
iteration. A restart is required for a change to take effect, same as
the other MFRC522/PN5180-specific settings (gain, PN5180 debounce).

Also fix a couple of pre-existing German wording issues found nearby
("RFID reader" -> "RFID-Reader", missing comma).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Settings sliders now show a live value badge and circular +/- step
buttons instead of a bare drag handle, sized off each slider's own
data-slider-step (falls back to 1). Existing decorative volume/battery/
hourglass icons are removed since the buttons take over that role, and
the label/value grid split (col-sm-4 + col-sm-8) is unwrapped so the
track gets the full row width on every screen size instead of being
squeezed into 2/3 of it.

Related settings across every tab (Allgemein sub-tabs, WLAN, MQTT, FTP,
Bluetooth, Updates, Tools) are now grouped into rounded cards instead of
being separated by <hr> lines, with consistent spacing between groups.
A few pre-existing `<span class="container">` wrappers in the Tools tab
were promoted to `<div>` - span is inline by default, so margin utility
classes on them were silently no-ops.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
NUM_INDICATOR_LEDS, NUM_CONTROL_LEDS, CONTROL_LEDS_COLORS,
NUM_LEDS_IDLE_DOTS, OFFSET_PAUSE_LEDS, PROGRESS_HUE_START,
PROGRESS_HUE_END, ATMO_HUE, ATMO_SATURATION, DIMMABLE_STATES and
LED_OFFSET only ever fed the pre-first-save NVS default; every one of
them has long been fully configurable at runtime via the web
interface, and none are board-specific or used as a compile-time array
bound (the LED buffer is heap-allocated at runtime sized off the NVS
values). Collapsed to a single hardcoded default matching the old
macro value.

CHIPSET/COLOR_ORDER are unaffected - those are genuine compile-time
FastLED template parameters and can never be runtime-configurable.

Breaking for custom builds: a settings-override.h still defining one
of the removed macros will silently no longer have any effect - set
the value once via the web UI instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not currently exploitable since the source is always a fixed-length
RFID tag ID, but matches the unterminated-copy pattern fixed for a
genuinely user-controlled path in biologist79#443.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…A) + silent wedge/removal disambiguation (biologist79#444)

* fix(pn5180): disambiguate a wedged reader from a removed card

The PN5180 can start returning corrupted responses and then stop
answering until it is reset (per the PN5180-Library author). A wedged
reader is indistinguishable from a removed card - reads just fail - so
declaring the card removed on the pn5180Debounce timeout alone pauses
playback on a card that is still resting on the reader.

When reads have failed for the whole debounce window, run one silent
re-init sweep before declaring removal: it re-runs the reader's own
RESET/SETUPRF/read states (the same sequence the post-removal path uses),
steered so the full cross-protocol sweep completes before the wedged
protocol is re-read, with removal suppressed throughout. If the card is
re-found it was a wedge and playback continues silently (the same-card
fast path leaves lastCardId intact, so no re-queue or pause); if the
sweep finds nothing it was really removed and removal is declared then.
One sweep per failure episode; a removed card is declared removed one
sweep after the debounce. Three LOGLEVEL_DEBUG lines (suspected /
recovered / gave up) make the behaviour visible on the serial log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(pn5180): deterministic ISO-14443 re-read via WUPA + HLTA

readCardSerial() polls with REQA (activateTypeA kind=0), which an
ISO-14443 PICC answers only from IDLE. After a successful read the PICC
is left SELECTed (ACTIVE) and ignores REQA until the RF field is
power-cycled, so a resting card is re-read only when the field happens to
be cycled and drops out otherwise.

Poll with WUPA (activateTypeA kind=1) and park the card in HALT
(mifareHalt) after each read instead: WUPA is answered from both IDLE and
HALT, so first detection (a freshly placed card is IDLE) and re-detection
(a card parked in HALT) are both covered with no field cycle, and a
resting card is re-read on every poll. A removed card answers neither, so
the read fails cleanly and the existing wedge-vs-removal sweep and
debounce run unchanged. readCardSerial() hardcodes REQA and leaves
mifareHalt() unused, so both are driven from a small local helper whose
UID validation matches readCardSerial(). The ISO-15693 path and the LPCD
WakeupCheck() are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…iologist79#413)

* RfidMfrc522: deterministic WUPA presence poll for pauseIfRfidRemoved

The card-still-present detection in pauseIfRfidRemoved mode was built on
PICC_IsNewCardPresent(), which sends REQA (0x26). REQA only invites cards
in the IDLE state; a card left ACTIVE after Select (as this mode did, by
skipping HaltA) never answers it. The old loop leaned on re-Select 'voodoo'
(miguelbalboa/rfid biologist79#188) whose success was non-deterministic on stationary
cards, causing pause/resume flapping while a card sat perfectly still on
the reader, and forcing a large miss debounce that only masked dropped
polls and added removal latency.

Fix: park the card in HALT after reading and poll with WUPA (0x52), the
only REQ-family command that wakes a HALTed card. Each poll is now a clean
yes/no. The new RfidMfrc522_CardStillPresent() template helper works for
both the SPI MFRC522 and I2C MFRC522_I2C classes via Reader:: constants
(their register addresses, StatusCode values, and WakeupA return types
differ). Removal debounce is 2 cycles (~100ms), purely for RF-noise
tolerance. Also hoists lastValidcardId out of the loop body so 'same card
reapplied' comparisons survive iterations (it was re-zeroed every pass).

Live-tested ~2h on ESP32-S3 + RC522 clone (SPI, version reg 0x82) with an
audiobook playlist: no flapping on stationary cards (38s+ continuous
presence verified), genuine removals detected in 100-400ms. Testing also
confirmed a related tuning note: with the default mfrc522Gain (7 = 48dB
max) a card lying directly ON the reader coil saturates the receiver and
gets phantom removals; mfrc522Gain=5 fixes contact distance. Full analysis
and test data in docs/rfid-presence-detection.md.

Both reader-class template instantiations compile (esp32-s3-devkitc-1 env
with RFID_READER_TYPE_RUNTIME + I2C_2_ENABLE).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeyF87wQMY3kVv3hz3UZSw

* Rfid: forget the last-seen tag when its assignment changes

Re-assigning an RFID-tag from the web UI had no effect until the next reboot or
deep-sleep cycle: presenting the tag kept starting the *old* book.

Both "same tag as last time" dedups live only in RAM and nothing invalidated them
when a tag's meaning changed:

  * pauseIfRfidRemoved: the reader task's own lastValidcardId buffer. When a tag is
    re-applied and the track/playlist is unfinished, the reader short-circuits it into
    a play/pause toggle and never puts it on the queue -- so Rfid_PreferenceLookupHandler
    never runs and NVS is never re-read. The freshly assigned book is never loaded and
    the previously loaded playlist simply resumes.
  * dontAcceptRfidTwice: gOldRfidTagId. This one was already reset on the rfidAssign
    path, but the reset was gated on dontAcceptRfidTwice itself, and the REST POST /rfid
    handler, the mod-card path, the delete path, the NVS-erase and the NVS-restore all
    missed it entirely.

Add Rfid_ResetLastTag(), which clears both, and call it from every path that changes
what a tag means. The reader's buffer is task-local, so it is cleared via an atomic
request flag that the reader task consumes at the top of its loop.

Also zero-initialise the PN5180 reader's lastValidcardId, which was comparing against
uninitialised stack memory.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pauseIfRfidRemoved's card-removed handler queued PAUSEPLAY unconditionally,
even when idle after natural playlist end - AudioPlayer_Cyclic() then
rejected it with a "no playmode change while idle" error, a confusing
message for what is just normal card removal after playback ended.

Also backfill changelog entries for biologist79#413, biologist79#442, biologist79#443 and biologist79#444, which were
merged without one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The runtime clamp (AudioPlayer_SetVolume honoring AudioPlayer_MinVolume)
already existed but was never fed from a setting, so it was stuck at 0.
Wire it up end to end:

- AudioPlayer_Init reads "minVolume" from NVS and applies it; the boot
  volume (initial or remembered-last) is raised to the floor if below it.
- Web.cpp save handler validates min < both maximums and rejects otherwise,
  plus settingsToJSON read-back and factory default (0).
- New "Minimal" slider in the volume settings card (0..21) with the same
  stepper/badge treatment as the sibling sliders, plus de/en/fr locales.
- Led.cpp volume animation maps from GetMinVolume() instead of a fixed 0.

Based on the idea in biologist79#392.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants