fix(ui): debounce the Hall sensor poll against reed-switch contact bounce

pollHallSensor() acted on the raw pin reading immediately, unlike every other
physical-input path in this file (MomentaryButton, pollCardKB()'s own
last-raw edge check). A cheap mechanical reed switch -- one of the two
sensor types the docs explicitly recommend wiring here, alongside a
solid-state Hall IC -- can chatter for a few ms while the magnet crosses the
trigger distance, so a poll every loop() tick during that window could flip
_locked and fire _display->turnOff()/turnOn() repeatedly in that short span:
wasted work on any panel, and a real cost on e-ink where each is a slow
full-panel operation.

A raw reading now has to hold steady for HALL_DEBOUNCE_MS (25ms, same
threshold as MomentaryButton's ISR_DEBOUNCE_MS) before it replaces
_hall_magnet_present and triggers the lock/unlock actions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-08-27 10:22:50 +02:00
co-authored by Claude Sonnet 5
parent a389a733d7
commit b71b46fd84
2 changed files with 23 additions and 3 deletions
+16 -3
View File
@@ -2255,11 +2255,24 @@ void UITask::pollCardKB() {
// there's nothing to show), opening unlocks and wakes it, with no combo or
// keypress either way. Independent of Auto-lock (Settings > Display), which is
// a timeout-driven setting -- this is a direct physical event.
//
// Debounced against a mechanical reed switch chattering for a few ms as the
// magnet crosses the trigger distance -- a raw flip only becomes the new
// _hall_magnet_present once it's been steady for HALL_DEBOUNCE_MS, so a bounce
// can't fire the lock/unlock actions (each including a full display
// off/on -- slow and disruptive on e-ink) more than once per real transition.
void UITask::pollHallSensor() {
#if defined(PIN_HALL_SENSOR)
bool present = HALL_ACTIVE_HIGH ? (digitalRead(PIN_HALL_SENSOR) == HIGH)
: (digitalRead(PIN_HALL_SENSOR) == LOW);
if (present == _hall_magnet_present) return;
bool raw = HALL_ACTIVE_HIGH ? (digitalRead(PIN_HALL_SENSOR) == HIGH)
: (digitalRead(PIN_HALL_SENSOR) == LOW);
if (raw != _hall_candidate) {
_hall_candidate = raw;
_hall_candidate_since = millis();
}
if (_hall_candidate == _hall_magnet_present) return; // no debounced change yet
if (millis() - _hall_candidate_since < HALL_DEBOUNCE_MS) return; // not steady long enough
bool present = _hall_candidate;
_hall_magnet_present = present;
if (present) { // cover closed
+7
View File
@@ -219,6 +219,13 @@ class UITask : public AbstractUITask {
#define HALL_ACTIVE_HIGH 0
#endif
bool _hall_magnet_present = false;
// Contact-bounce guard for a mechanical reed switch (a Hall-effect IC reads
// clean, but the docs recommend either): a raw reading only replaces
// _hall_magnet_present once it's held steady for HALL_DEBOUNCE_MS, same
// threshold and reasoning as MomentaryButton's ISR_DEBOUNCE_MS.
static const uint32_t HALL_DEBOUNCE_MS = 25;
bool _hall_candidate = false;
uint32_t _hall_candidate_since = 0;
#endif
void pollHallSensor();