feat(ui): optional magnetic flip-cover screen lock via user-wired Hall sensor

No board in this repo has one built in, and no default pin is assumed
anywhere -- whoever wires a Hall-effect or reed sensor to a free GPIO sets
PIN_HALL_SENSOR (and HALL_ACTIVE_HIGH, for a sensor that pulls the pin high
rather than low on presence) as a build_flag on their own env. Entirely
opt-in and a no-op elsewhere, same pattern as PIN_GPIO1..4/ADC_MULTIPLIER/
CARDKB_ENABLE.

Level-triggered polling (like pollCardKB()) rather than an edge interrupt --
a magnet held near the sensor reads the same way every tick, so the new
pollHallSensor() only acts on the two transitions. Closing locks and blanks
the display with no wake grace (the cover is physically over the screen, so
there's nothing to show); opening unlocks and wakes it, with no key combo
either way. Both are independent of the Auto-lock setting, which is a
timeout, not a physical event.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-08-26 13:48:33 +02:00
co-authored by Claude Sonnet 5
parent 13fb4a189f
commit 58e6bfcec5
4 changed files with 69 additions and 0 deletions
@@ -51,3 +51,14 @@ The display turns off again automatically after 5 seconds of inactivity (or 2 se
### Auto-lock
Enable **Auto-lock** in **Settings Display** to lock the device automatically whenever the display turns off due to auto-off timeout.
---
### Magnetic cover (Hall sensor)
Optional, user-supplied hardware — no board in this repo has one built in. Wire a Hall-effect or reed sensor to any free GPIO, then set `PIN_HALL_SENSOR` (and `HALL_ACTIVE_HIGH=1`, if your module pulls the pin high rather than low when the magnet is present) as `build_flags` in your own env. No-op entirely unless `PIN_HALL_SENSOR` is defined.
Fully autonomous, independent of Auto-lock and of any key combo:
- **Magnet near (cover closed)** — locks and blanks the display immediately, no wake grace.
- **Magnet away (cover opened)** — unlocks and wakes the display right away.
@@ -1404,6 +1404,17 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
_has_cardkb = (Wire1.endTransmission() == 0);
#endif
#if defined(PIN_HALL_SENSOR)
// Internal pull matches the default polarity: pulled up so an active-low
// sensor reads HIGH at rest, pulled down so an active-high one reads LOW at
// rest. Most reed/Hall breakouts are open-drain, active-low -- HALL_ACTIVE_HIGH
// is only for modules wired the other way.
pinMode(PIN_HALL_SENSOR, HALL_ACTIVE_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
_hall_magnet_present = HALL_ACTIVE_HIGH ? (digitalRead(PIN_HALL_SENSOR) == HIGH)
: (digitalRead(PIN_HALL_SENSOR) == LOW);
if (_hall_magnet_present) _locked = true; // booting with the cover already closed
#endif
#if defined(PIN_USER_BTN)
user_btn.begin();
#endif
@@ -2237,6 +2248,37 @@ void UITask::pollCardKB() {
#endif
}
// Level-triggered, like pollCardKB() -- a magnet held near the sensor reads the
// same way every tick, so this only acts on the two edges (closed/opened), not
// on every poll. Fully autonomous: closing locks and blanks the display
// immediately (no wake grace -- the cover is physically over the screen, so
// 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.
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;
_hall_magnet_present = present;
if (present) { // cover closed
_locked = true;
_lock_wake_until = 0;
if (_display) _display->turnOff();
#ifdef PIN_LED
digitalWrite(PIN_LED, LOW); // same as the auto-off path -- one less thing lit under a closed cover
#endif
} else { // cover opened
_locked = false;
if (_display && !_display->isOn()) _display->turnOn();
uint32_t aoff = autoOffMillis();
if (aoff > 0) _auto_off = millis() + aoff;
}
_next_refresh = 0;
#endif
}
void UITask::loop() {
// Background delivery: resend pending on-device DMs whose ACK timed out, and
// finalise the ✗ marker — runs regardless of which screen is active.
@@ -2335,6 +2377,7 @@ void UITask::loop() {
}
#endif
pollCardKB();
pollHallSensor();
#ifdef ENV_USE_TCA8418
{
extern char tca8418_keypad_read(); // provided by the active variant
+14
View File
@@ -208,6 +208,20 @@ class UITask : public AbstractUITask {
#endif
void pollCardKB();
// Optional magnetic "flip cover" lock: a Hall-effect or reed sensor wired to
// any free GPIO, closing (pulling active) when a magnet is near. Entirely
// user-supplied -- no board in this repo ships one, so PIN_HALL_SENSOR must
// be added as a build_flag by whoever wires the sensor up; no-op everywhere
// else. HALL_ACTIVE_HIGH overrides the default polarity for modules that
// pull the pin HIGH (instead of LOW) when the magnet is present.
#if defined(PIN_HALL_SENSOR)
#ifndef HALL_ACTIVE_HIGH
#define HALL_ACTIVE_HIGH 0
#endif
bool _hall_magnet_present = false;
#endif
void pollHallSensor();
void setCurrScreen(UIScreen* c);
// Centred alert overlay (the showAlert() box). Wraps long text to up to
+1
View File
@@ -3,6 +3,7 @@
### What's new
- **On-device community scope, and repeater-side scope filtering.** Settings Radio gets a **Scope** field — type a region/community name (e.g. `pl`) and every device typing the same name derives the same shared tag, no key exchange needed; it's what your own DM/channel sends carry, previously only settable from a connected app. Tools Repeater gains **Scope only** (only relay flood traffic matching your own scope, or one of the new **Extra scopes** below — a no-op until a scope is actually set, so it can't silently blackhole all forwarding) and **Extra scopes** (comma-separated additional regions to relay for, without changing what scope this device's own messages send under).
- **Optional magnetic "flip cover" screen lock**, for anyone who wants to wire a Hall-effect or reed sensor to a free GPIO — no board ships one built in. Set `PIN_HALL_SENSOR` as a build flag on your own env and closing the cover locks and blanks the screen instantly, opening it unlocks and wakes it — no combo, independent of Auto-lock. See [Screen Lock](docs/solo_features/screen_lock/screen_lock.md#magnetic-cover-hall-sensor).
### Fixes