From 385c88592a026532d556696b3fbc6e0dea9cd91a Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 21 May 2026 13:04:54 +0200 Subject: [PATCH] joystick UI: kill heartbeat + type the screen members MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-off and auto-lock were the last polling-style deadline checks. display.c already owns the auto-off (k_work_delayable rescheduled via mc_display_reset_auto_off()), so its tracker in JoystickUITask was fully redundant — remove _auto_off and the loop() check. Auto-lock becomes a one-shot k_timer (_lock_timer) scheduled per activity; ISR callback sets _locked + signals refresh. The 2-second heartbeat timer in joystick_ui_hooks is gone, along with the start/stopHeartbeatFns plumbing it served. The mesh thread now wakes only on actual events: input, mesh, screen-owned timers, the lock timer, and display.c's own auto-off work. Also: screen member pointers in JoystickUITask are now their concrete subclass types instead of UIScreen *; removes 14 static_cast<>s at call sites. _curr stays UIScreen * (polymorphic). --- .../helpers/ui-joystick/joystick_ui_hooks.cpp | 19 ---- .../helpers/ui-joystick/joystick_ui_task.cpp | 97 ++++++++++--------- .../helpers/ui-joystick/joystick_ui_task.h | 56 ++++++----- 3 files changed, 76 insertions(+), 96 deletions(-) diff --git a/zephcore/helpers/ui-joystick/joystick_ui_hooks.cpp b/zephcore/helpers/ui-joystick/joystick_ui_hooks.cpp index fd4c06a..49008d2 100644 --- a/zephcore/helpers/ui-joystick/joystick_ui_hooks.cpp +++ b/zephcore/helpers/ui-joystick/joystick_ui_hooks.cpp @@ -45,30 +45,13 @@ static void joystick_render_timer_fn(struct k_timer *timer) if (s_signal_refresh) s_signal_refresh(); } -static void joystick_heartbeat_timer_fn(struct k_timer *timer) -{ - ARG_UNUSED(timer); - if (s_signal_refresh) s_signal_refresh(); -} - K_TIMER_DEFINE(s_joystick_render_timer, joystick_render_timer_fn, NULL); -K_TIMER_DEFINE(s_joystick_heartbeat_timer, joystick_heartbeat_timer_fn, NULL); static void joystick_schedule_render(uint32_t delay_ms) { k_timer_start(&s_joystick_render_timer, K_MSEC(delay_ms), K_NO_WAIT); } -static void joystick_start_heartbeat(void) -{ - k_timer_start(&s_joystick_heartbeat_timer, K_MSEC(2000), K_MSEC(2000)); -} - -static void joystick_stop_heartbeat(void) -{ - k_timer_stop(&s_joystick_heartbeat_timer); -} - extern "C" void joystick_ui_hooks_register( void *task, ui_signal_fn signal_refresh, @@ -79,8 +62,6 @@ extern "C" void joystick_ui_hooks_register( s_signal_tx = signal_tx; JoystickUITask::setSignalFn(signal_refresh); JoystickUITask::setScheduleRenderFn(joystick_schedule_render); - JoystickUITask::setHeartbeatFns(joystick_start_heartbeat, joystick_stop_heartbeat); - joystick_start_heartbeat(); } extern "C" void ui_signal_refresh(void) diff --git a/zephcore/helpers/ui-joystick/joystick_ui_task.cpp b/zephcore/helpers/ui-joystick/joystick_ui_task.cpp index a1e8123..8e7f31d 100644 --- a/zephcore/helpers/ui-joystick/joystick_ui_task.cpp +++ b/zephcore/helpers/ui-joystick/joystick_ui_task.cpp @@ -50,15 +50,26 @@ struct k_msgq JoystickUITask::_key_queue; void (*JoystickUITask::s_signal_fn)(void) = nullptr; void (*JoystickUITask::s_schedule_render_fn)(uint32_t) = nullptr; -void (*JoystickUITask::s_start_heartbeat_fn)(void) = nullptr; -void (*JoystickUITask::s_stop_heartbeat_fn)(void) = nullptr; void JoystickUITask::setSignalFn(void (*fn)(void)) { s_signal_fn = fn; } void JoystickUITask::setScheduleRenderFn(void (*fn)(uint32_t)) { s_schedule_render_fn = fn; } -void JoystickUITask::setHeartbeatFns(void (*start_fn)(void), void (*stop_fn)(void)) + +void JoystickUITask::lockTimerCb(struct k_timer *t) { - s_start_heartbeat_fn = start_fn; - s_stop_heartbeat_fn = stop_fn; + /* ISR — atomic writes to mark locked; signal refresh so the loop wakes + * and the lock overlay renders. */ + auto *self = static_cast(k_timer_user_data_get(t)); + if (!self) return; + self->_locked = true; + self->_lock_step = 0; + self->_next_refresh = 0; + if (s_signal_fn) s_signal_fn(); +} + +void JoystickUITask::scheduleLockTimer() +{ + k_timer_stop(&_lock_timer); + k_timer_start(&_lock_timer, K_MSEC(LOCK_AFTER_MS), K_NO_WAIT); } static bool joystick_queue_initialized; @@ -174,12 +185,12 @@ JoystickUITask::JoystickUITask() _doom(nullptr), #endif _repeater_admin(nullptr), _curr(nullptr), - _next_refresh(0), _auto_off(0), _screen_off_ms(AUTO_OFF_MILLIS), + _next_refresh(0), _screen_off_ms(AUTO_OFF_MILLIS), _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), _pkt_recv(0), _pkt_sent(0), _pkt_errors(0), _alert_expiry(0), - _locked(false), _lock_at(0), _lock_step(0), + _locked(false), _lock_step(0), _compose_is_contact(false), _compose_channel_idx(-1), _ch_preview_count(0), _ch_preview_head(JOYSTICK_OFFLINE_QUEUE_SIZE - 1), _started_at(0), _initialized(false) @@ -214,8 +225,11 @@ void JoystickUITask::begin(BaseChatMesh *mesh, mesh::ZephyrRTCClock *rtc, NodePr } } applyBrightness(); - _auto_off = _started_at + _screen_off_ms; - _lock_at = _started_at + LOCK_AFTER_MS; + + /* Lock timer: fires once when LOCK_AFTER_MS passes without activity. */ + k_timer_init(&_lock_timer, lockTimerCb, NULL); + k_timer_user_data_set(&_lock_timer, this); + scheduleLockTimer(); /* Init key queue */ k_msgq_init(&_key_queue, _key_buf, sizeof(char), JOYSTICK_KEY_QUEUE_DEPTH); @@ -289,14 +303,14 @@ void JoystickUITask::gotoDoomScreen() { setCurrScreen(_doom); } void JoystickUITask::gotoUnreadScreen() { if (!_unread) return; - static_cast(_unread)->activatePreview(false, 0); + _unread->activatePreview(false, 0); setCurrScreen(_unread); } void JoystickUITask::gotoContactForAdvert(const uint8_t *prefix, int prefix_len) { if (!_contacts) return; - static_cast(_contacts)->openForPubKey(prefix, prefix_len); + _contacts->openForPubKey(prefix, prefix_len); setCurrScreen(_contacts); } @@ -304,7 +318,7 @@ void JoystickUITask::gotoRepeaterAdminScreen(const uint8_t *pub_key, const char bool from_contacts) { if (!_repeater_admin) return; - static_cast(_repeater_admin)->openForContact(pub_key, name, + _repeater_admin->openForContact(pub_key, name, from_contacts); setCurrScreen(_repeater_admin); } @@ -359,8 +373,7 @@ void JoystickUITask::handleLockInput(char key, uint32_t now) if (key == KEY_CANCEL) { _locked = false; _lock_step = 0; - _lock_at = now + LOCK_AFTER_MS; - if (s_start_heartbeat_fn) s_start_heartbeat_fn(); + scheduleLockTimer(); } else { _lock_step = 0; } @@ -407,7 +420,7 @@ void JoystickUITask::renderLockOverlay() mc_display_text((w - batt_w) / 2, 6 + fh + 6, batt_buf, false); char unread_buf[16]; - int unread = (_unread) ? static_cast(_unread)->getUnreadCount() : 0; + int unread = (_unread) ? _unread->getUnreadCount() : 0; snprintf(unread_buf, sizeof(unread_buf), "Unread: %d", unread); int unread_w = (int)strlen(unread_buf) * fw; mc_display_text((w - unread_w) / 2, 6 + fh + 6 + fh + 2, unread_buf, false); @@ -442,18 +455,11 @@ void JoystickUITask::loop() uint32_t now = k_uptime_get_32(); - /* Auto off */ - if (_display.isOn() && now >= _auto_off) { - _display.turnOff(); - } - - /* Auto lock */ - if (!_locked && now >= _lock_at) { - _locked = true; - _lock_step = 0; - _next_refresh = 0; - if (s_stop_heartbeat_fn) s_stop_heartbeat_fn(); - } + /* Auto-off is owned by display.c (k_work_delayable in display.c rescheduled + * via mc_display_reset_auto_off() — fires mc_display_off() when due). + * Auto-lock is owned by _lock_timer (fires lockTimerCb in ISR which sets + * _locked = true and signals refresh). Both are fully event-driven; no + * deadline checks needed here. */ /* Dequeue key events */ char key = 0; @@ -464,19 +470,17 @@ 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() */ - _auto_off = now + _screen_off_ms; _next_refresh = 0; goto do_render; } - /* Reset auto off on any keypress */ + /* Reset auto-off (display.c) on any keypress */ mc_display_reset_auto_off(); - _auto_off = now + _screen_off_ms; if (_locked) { handleLockInput(key, now); } else { - _lock_at = now + LOCK_AFTER_MS; + scheduleLockTimer(); /* push lock deadline forward */ bool consumed = false; if (key == KEY_BUZZ_TOGGLE) { toggleBuzzer(); @@ -633,7 +637,6 @@ void JoystickUITask::adjustScreenOff(int32_t delta_ms) if (v < 5000) v = 5000; if (v > 300000) v = 300000; _screen_off_ms = (uint32_t)v; - _auto_off = k_uptime_get_32() + _screen_off_ms; mc_display_set_auto_off_ms(_screen_off_ms); mc_display_reset_auto_off(); mesh_save_screen_off_secs((uint16_t)(_screen_off_ms / 1000)); @@ -652,13 +655,13 @@ void JoystickUITask::newMsg(uint8_t path_len, const char *from_name, const char if (_unread) { /* When BLE phone is connected it pulls offline queue and marks read; * keep the message in our local history but don't count as unread. */ - static_cast(_unread)->addPreview(path_len, from_name, text, + _unread->addPreview(path_len, from_name, text, /*initially_read=*/_ble_connected); } - /* Wake display if wake on msg is enabled and no BLE peer is connected */ + /* Wake display if wake on msg is enabled and no BLE peer is connected. + * mc_display_on() already reschedules display.c's auto-off timer. */ if (_wake_on_msg && !_ble_connected && !_display.isOn()) { - _display.turnOn(); /* mc_display_on() already calls mc_display_reset_auto_off() */ - _auto_off = k_uptime_get_32() + _screen_off_ms; + _display.turnOn(); } _next_refresh = 0; #ifdef CONFIG_ZEPHCORE_UI_BUZZER @@ -687,12 +690,11 @@ void JoystickUITask::newChannelMsg(const char *channel_name, const char *text, char ch_from[48]; snprintf(ch_from, sizeof(ch_from), "#%s", channel_name ? channel_name : "?"); /* BLE-connected → phone reads via offline queue, don't count as unread. */ - static_cast(_unread)->addPreview(path_len, ch_from, text, + _unread->addPreview(path_len, ch_from, text, /*initially_read=*/_ble_connected); } if (_wake_on_msg && !_ble_connected && !_display.isOn()) { - _display.turnOn(); - _auto_off = k_uptime_get_32() + _screen_off_ms; + _display.turnOn(); /* display.c reschedules auto-off internally */ } _next_refresh = 0; #ifdef CONFIG_ZEPHCORE_UI_BUZZER @@ -720,7 +722,7 @@ void JoystickUITask::notify() int JoystickUITask::getUnreadCount() { if (!_unread) return 0; - return static_cast(_unread)->getUnreadCount(); + return _unread->getUnreadCount(); } int JoystickUITask::getStoredMsgCount() const @@ -812,7 +814,7 @@ bool JoystickUITask::sendComposedMessage(const char *text) ui_signal_tx(); if (_unread) { /* Sent message: in history but not "unread" (user sent it). */ - static_cast(_unread)->addPreview(OUT_PATH_SENT, _compose_contact_name, text, + _unread->addPreview(OUT_PATH_SENT, _compose_contact_name, text, /*initially_read=*/true); } /* Notify BLE app so it can mirror the sent message in its UI. */ @@ -897,9 +899,8 @@ void JoystickUITask::onRepeaterAdminLoginResult(const uint8_t *pub_key_prefix, bool success, uint8_t permissions, uint32_t server_time) { if (!_repeater_admin || _curr != _repeater_admin) return; - auto *s = static_cast(_repeater_admin); (void)pub_key_prefix; - s->onLoginResult(success, permissions, server_time); + _repeater_admin->onLoginResult(success, permissions, server_time); _next_refresh = 0; ui_signal_refresh(); } @@ -909,7 +910,7 @@ void JoystickUITask::onRepeaterAdminCliResponse(const uint8_t *pub_key_prefix, { if (!_repeater_admin || _curr != _repeater_admin) return; (void)pub_key_prefix; - static_cast(_repeater_admin)->onCliResponse(text); + _repeater_admin->onCliResponse(text); _next_refresh = 0; ui_signal_refresh(); } @@ -922,7 +923,7 @@ void JoystickUITask::onRepeaterReqResponse(const uint8_t *pub_key_prefix, { /* Route binary response to repeater admin screen if it's active and waiting */ if (_repeater_admin && _curr == _repeater_admin && data && data_len > 0) { - static_cast(_repeater_admin)->onReqResponse(pub_key_prefix, + _repeater_admin->onReqResponse(pub_key_prefix, data, data_len); _next_refresh = 0; ui_signal_refresh(); @@ -934,7 +935,7 @@ void JoystickUITask::onRepeaterReqResponse(const uint8_t *pub_key_prefix, memset(_pending_ping_pubkey, 0, sizeof(_pending_ping_pubkey)); _pending_ping_sent_ms = 0; if (!_contacts || _curr != _contacts) return; - static_cast(_contacts)->onPingResponse(snr_local, snr_remote, rtt_ms); + _contacts->onPingResponse(snr_local, snr_remote, rtt_ms); _next_refresh = 0; ui_signal_refresh(); } @@ -971,10 +972,10 @@ void JoystickUITask::notifyPacketSent() _pending_ping_sent_ms = k_uptime_get_32(); } if (_contacts && _curr == _contacts) { - static_cast(_contacts)->onPacketSent(); + _contacts->onPacketSent(); } if (_repeater_admin && _curr == _repeater_admin) { - static_cast(_repeater_admin)->onPacketSent(); + _repeater_admin->onPacketSent(); } } diff --git a/zephcore/helpers/ui-joystick/joystick_ui_task.h b/zephcore/helpers/ui-joystick/joystick_ui_task.h index 4d23ac4..b9b8320 100644 --- a/zephcore/helpers/ui-joystick/joystick_ui_task.h +++ b/zephcore/helpers/ui-joystick/joystick_ui_task.h @@ -184,52 +184,51 @@ public: /* Set by joystick_ui_hooks after registration */ static void setSignalFn(void (*fn)(void)); static void setScheduleRenderFn(void (*fn)(uint32_t delay_ms)); - static void setHeartbeatFns(void (*start_fn)(void), void (*stop_fn)(void)); private: static void (*s_signal_fn)(void); static void (*s_schedule_render_fn)(uint32_t); - static void (*s_start_heartbeat_fn)(void); - static void (*s_stop_heartbeat_fn)(void); JoystickDisplay _display; BaseChatMesh *_mesh; mesh::ZephyrRTCClock *_rtc; NodePrefs *_prefs; /* Screen instances */ - UIScreen *_splash; - UIScreen *_home; - UIScreen *_contacts; - UIScreen *_unread; - UIScreen *_advert; - UIScreen *_gps; - UIScreen *_system; - UIScreen *_system_time; - UIScreen *_telemetry; - UIScreen *_tools; - UIScreen *_radio_stats; - UIScreen *_repeaters; - UIScreen *_channels; - UIScreen *_t9_input; - UIScreen *_rename_node; - UIScreen *_ble_code; - UIScreen *_stats; - UIScreen *_stopwatch; - UIScreen *_countdown; - UIScreen *_snake; + SplashScreen *_splash; + HomeScreen *_home; + ContactsScreen *_contacts; + UnreadScreen *_unread; + AdvertScreen *_advert; + GPSSettingsScreen *_gps; + SystemScreen *_system; + SystemTimeScreen *_system_time; + TelemetryScreen *_telemetry; + ToolsScreen *_tools; + RadioStatsScreen *_radio_stats; + RepeatersScreen *_repeaters; + ChannelsScreen *_channels; + T9InputScreen *_t9_input; + RenameNodeScreen *_rename_node; + BLECodeScreen *_ble_code; + StatsScreen *_stats; + StopwatchScreen *_stopwatch; + CountdownScreen *_countdown; + SnakeScreen *_snake; #ifdef CONFIG_ZEPHCORE_EASTER_EGG_DOOM - UIScreen *_doom; + DoomScreen *_doom; #endif - UIScreen *_repeater_admin; - UIScreen *_curr; + RepeaterAdminScreen *_repeater_admin; + UIScreen *_curr; /* polymorphic — points at whichever subclass is active */ void setCurrScreen(UIScreen *s); void applyBrightness(); /* Timing */ uint32_t _next_refresh; - uint32_t _auto_off; - uint32_t _screen_off_ms; + uint32_t _screen_off_ms; /* user setting; display.c owns the auto-off timer */ + struct k_timer _lock_timer; /* one-shot, fires LOCK_AFTER_MS after last activity */ + static void lockTimerCb(struct k_timer *t); + void scheduleLockTimer(); /* State */ uint16_t _cached_batt_mv; @@ -265,7 +264,6 @@ private: /* Screen lock */ static constexpr uint32_t LOCK_AFTER_MS = 60000UL; bool _locked; - uint32_t _lock_at; uint8_t _lock_step; void renderLockOverlay(); void handleLockInput(char key, uint32_t now);