mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 20:09:17 +00:00
joystick UI: pause periodic timers on display-off, reset lock on wake
UIScreen gains onDisplayOff()/onDisplayOn() hooks. JoystickUITask tracks display state and dispatches them on transition — at top of loop() (catches display.c's auto-off, which fires behind our back) and right after _display.turnOn() in the wake path (immediate resume). Override in SnakeScreen and GPSSettingsScreen: stop their periodic k_timers while the screen is off, restart on wake. Game state and GPS-fix state are preserved across sleep. Snake doesn't crash into a wall five seconds after the screen sleeps anymore. Other screen timers (Countdown alarm, Contacts/Admin response timeout, the global lock timer) intentionally keep running — their job is to fire while the user is idle. Also: wake-from-off now reschedules the lock timer if not already locked, so a keypress near the end of the lock window gives you a fresh LOCK_AFTER_MS instead of being immediately re-locked.
This commit is contained in:
@@ -21,13 +21,20 @@ class JoystickUITask;
|
||||
|
||||
/* ===== UIScreen base class =====
|
||||
* Lifecycle:
|
||||
* onEnter() — called by JoystickUITask::setCurrScreen when this screen
|
||||
* becomes active. Start any per-screen k_timer here.
|
||||
* onExit() — called when navigating away from this screen. Stop any
|
||||
* per-screen k_timer here so it can't fire on a stale screen.
|
||||
* render() — draws the current state. Triggered by signals (key event,
|
||||
* mesh event, screen-owned timer fire), never polled.
|
||||
* handleInput() — receives one queued key character.
|
||||
* onEnter() — called by setCurrScreen when this screen becomes
|
||||
* active. Start any per-screen k_timer here.
|
||||
* onExit() — called when navigating away. Stop any per-screen
|
||||
* k_timer here so it can't fire on a stale screen.
|
||||
* onDisplayOff() — called when the display turns off while this screen
|
||||
* is active. Pause periodic timers whose work is
|
||||
* invisible while the screen is off (e.g. game ticks,
|
||||
* GPS sampling). Keep one-shot timers running if their
|
||||
* purpose is to fire while idle (alarms, timeouts).
|
||||
* onDisplayOn() — counterpart of onDisplayOff; resume what you paused.
|
||||
* render() — draws the current state. Triggered by signals (key
|
||||
* event, mesh event, screen-owned timer fire), never
|
||||
* polled.
|
||||
* handleInput() — receives one queued key character.
|
||||
*/
|
||||
class UIScreen {
|
||||
public:
|
||||
@@ -36,6 +43,8 @@ public:
|
||||
virtual bool handleInput(char c) { (void)c; return false; }
|
||||
virtual void onEnter() {}
|
||||
virtual void onExit() {}
|
||||
virtual void onDisplayOff() {}
|
||||
virtual void onDisplayOn() {}
|
||||
};
|
||||
|
||||
/* ===== Admin command size limits ===== */
|
||||
@@ -111,6 +120,8 @@ public:
|
||||
bool handleInput(char c) override;
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void onDisplayOff() override;
|
||||
void onDisplayOn() override;
|
||||
};
|
||||
|
||||
/* ===== SystemScreen ===== */
|
||||
@@ -323,6 +334,8 @@ public:
|
||||
bool handleInput(char c) override;
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void onDisplayOff() override;
|
||||
void onDisplayOn() override;
|
||||
};
|
||||
|
||||
/* ===== DoomScreen ===== */
|
||||
|
||||
@@ -72,6 +72,17 @@ void JoystickUITask::scheduleLockTimer()
|
||||
k_timer_start(&_lock_timer, K_MSEC(LOCK_AFTER_MS), K_NO_WAIT);
|
||||
}
|
||||
|
||||
void JoystickUITask::onDisplayStateChanged()
|
||||
{
|
||||
bool is_on = _display.isOn();
|
||||
if (is_on == _was_display_on) return;
|
||||
if (_curr) {
|
||||
if (is_on) _curr->onDisplayOn();
|
||||
else _curr->onDisplayOff();
|
||||
}
|
||||
_was_display_on = is_on;
|
||||
}
|
||||
|
||||
static bool joystick_queue_initialized;
|
||||
|
||||
#define ENTER_LONG_PRESS_MS 500
|
||||
@@ -185,7 +196,7 @@ JoystickUITask::JoystickUITask()
|
||||
_doom(nullptr),
|
||||
#endif
|
||||
_repeater_admin(nullptr), _curr(nullptr),
|
||||
_next_refresh(0), _screen_off_ms(AUTO_OFF_MILLIS),
|
||||
_next_refresh(0), _screen_off_ms(AUTO_OFF_MILLIS), _was_display_on(false),
|
||||
_cached_batt_mv(0), _battery_display_mode(0),
|
||||
_brightness(100), _wake_on_msg(true), _ble_connected(false),
|
||||
_ble_enabled(true), _msgcount(0), _noise_floor(-120),
|
||||
@@ -461,6 +472,11 @@ void JoystickUITask::loop()
|
||||
* _locked = true and signals refresh). Both are fully event-driven; no
|
||||
* deadline checks needed here. */
|
||||
|
||||
/* Catch display state transitions (mainly display.c's auto-off, which
|
||||
* happens behind our back) so the current screen can pause/resume any
|
||||
* periodic timers it owns. */
|
||||
onDisplayStateChanged();
|
||||
|
||||
/* Dequeue key events */
|
||||
char key = 0;
|
||||
if (k_msgq_get(&_key_queue, &key, K_NO_WAIT) == 0) {
|
||||
@@ -470,6 +486,10 @@ void JoystickUITask::loop()
|
||||
* for the lock screen, which the user is about to see. */
|
||||
ui_invalidate_battery_cache();
|
||||
_display.turnOn(); /* mc_display_on() already calls mc_display_reset_auto_off() */
|
||||
onDisplayStateChanged(); /* dispatch onDisplayOn now, no one-frame delay */
|
||||
/* Push the lock deadline forward so a wake-press at the tail end
|
||||
* of the lock window doesn't get re-locked immediately. */
|
||||
if (!_locked) scheduleLockTimer();
|
||||
_next_refresh = 0;
|
||||
goto do_render;
|
||||
}
|
||||
|
||||
@@ -229,6 +229,8 @@ private:
|
||||
struct k_timer _lock_timer; /* one-shot, fires LOCK_AFTER_MS after last activity */
|
||||
static void lockTimerCb(struct k_timer *t);
|
||||
void scheduleLockTimer();
|
||||
bool _was_display_on; /* previous display state for transition detection */
|
||||
void onDisplayStateChanged(); /* dispatches onDisplayOff/On to _curr on transition */
|
||||
|
||||
/* State */
|
||||
uint16_t _cached_batt_mv;
|
||||
|
||||
@@ -646,6 +646,18 @@ void GPSSettingsScreen::onExit()
|
||||
k_timer_stop(&_sample_timer);
|
||||
}
|
||||
|
||||
void GPSSettingsScreen::onDisplayOff()
|
||||
{
|
||||
/* Pause the 1 Hz GPS sample loop while the screen is off — the user
|
||||
* can't see speed/heading and we're just burning ADC + math. */
|
||||
k_timer_stop(&_sample_timer);
|
||||
}
|
||||
|
||||
void GPSSettingsScreen::onDisplayOn()
|
||||
{
|
||||
k_timer_start(&_sample_timer, K_MSEC(1000), K_MSEC(1000));
|
||||
}
|
||||
|
||||
void GPSSettingsScreen::sampleGPS()
|
||||
{
|
||||
if (!_task->isGPSAvailable() || !_task->getGPSState()) return;
|
||||
|
||||
@@ -491,6 +491,20 @@ void SnakeScreen::onExit()
|
||||
_tick_due = false;
|
||||
}
|
||||
|
||||
void SnakeScreen::onDisplayOff()
|
||||
{
|
||||
/* Pause the game-tick timer while the screen is off — no point burning
|
||||
* wakes to advance a snake nobody can see. The game state is preserved;
|
||||
* we'll resume on onDisplayOn(). */
|
||||
k_timer_stop(&_tick_timer);
|
||||
_tick_due = false;
|
||||
}
|
||||
|
||||
void SnakeScreen::onDisplayOn()
|
||||
{
|
||||
if (s_state == STATE_PLAYING) startTicking();
|
||||
}
|
||||
|
||||
void SnakeScreen::reset()
|
||||
{
|
||||
int cx = GRID_W / 2, cy = GRID_H / 2;
|
||||
|
||||
Reference in New Issue
Block a user