diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index 13234b0..1060f0e 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -430,7 +430,6 @@ target_sources(app PRIVATE adapters/sensors/ZephyrEnvSensors.cpp helpers/AdvertDataHelpers.cpp helpers/oled_power.c - helpers/time_sync.c ) # ========== Radio Driver Selection ========== @@ -615,6 +614,7 @@ endif() if(CONFIG_ZEPHCORE_UI_DISPLAY AND CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) target_sources(app PRIVATE + helpers/ui-joystick/time_sync.c helpers/ui-joystick/joystick_ui_task.cpp helpers/ui-joystick/joystick_ui_hooks.cpp helpers/ui-joystick/screens/home.cpp diff --git a/zephcore/app/CompanionMesh.cpp b/zephcore/app/CompanionMesh.cpp index d40f3ee..b134a41 100644 --- a/zephcore/app/CompanionMesh.cpp +++ b/zephcore/app/CompanionMesh.cpp @@ -22,9 +22,7 @@ #include #define ZEPHCORE_HAS_UI_TASK 1 #endif -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) #include -#endif LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL); /* Protocol commands (matches Arduino companion_radio) - sorted by opcode */ @@ -697,18 +695,12 @@ ContactInfo *CompanionMesh::processAck(const uint8_t *data) return lookupContactByPubKey(ci.id.pub_key, PUB_KEY_SIZE); } } -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) - /* Joystick UI tracks its own pending DMs; let it match before falling - * through to connection-keep-alive ACKs. The joystick handles delivery - * indicator + BLE-mirror queueing internally — we just need the matched - * contact pointer for BaseChatMesh's return-path-retry hook. */ { uint8_t recipient_pubkey[6]; if (ui_joystick_try_match_ack(ack_crc, recipient_pubkey)) { return lookupContactByPubKey(recipient_pubkey, 6); } } -#endif return checkConnectionsAck(data); } @@ -887,12 +879,10 @@ void CompanionMesh::onCommandDataRecv(const ContactInfo &contact, mesh::Packet * markConnectionActive(contact); queueContactMessage(contact, pkt, TXT_TYPE_CLI_DATA, sender_timestamp, nullptr, 0, text); sendPush(PUSH_CODE_MSG_WAITING); -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) { struct ui_joystick_cli_data cli = { text }; ui_notify_joystick_event(UI_JOYSTICK_CLI_RESPONSE, contact.id.pub_key, &cli); } -#endif } void CompanionMesh::onSignedMessageRecv(const ContactInfo &contact, mesh::Packet *pkt, @@ -1190,9 +1180,7 @@ uint8_t CompanionMesh::onContactRequest(const ContactInfo &contact, uint32_t sen void CompanionMesh::logTx(mesh::Packet *, int) { -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) ui_notify_packet_sent(); -#endif } void CompanionMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) @@ -1243,7 +1231,6 @@ void CompanionMesh::onContactResponse(const ContactInfo &contact, const uint8_t i += 6; } sendPush(rsp[0], &rsp[1], i - 1); -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) { struct ui_joystick_login_data login = { rsp[0] == PUSH_CODE_LOGIN_SUCCESS, @@ -1252,7 +1239,6 @@ void CompanionMesh::onContactResponse(const ContactInfo &contact, const uint8_t }; ui_notify_joystick_event(UI_JOYSTICK_LOGIN_RESULT, contact.id.pub_key, &login); } -#endif return; } @@ -1340,10 +1326,6 @@ void CompanionMesh::onContactResponse(const ContactInfo &contact, const uint8_t return; } -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) - /* Unmatched response: forward to joystick so it can check for a pending ping. - * RepeaterStats.last_snr sits at byte offset 42 in the struct payload (after the - * 4-byte tag prefix), so data[46]. Only valid when the response is long enough. */ { int8_t snr_remote = INT8_MIN; if (len >= 48) { @@ -1359,7 +1341,6 @@ void CompanionMesh::onContactResponse(const ContactInfo &contact, const uint8_t }; ui_notify_joystick_event(UI_JOYSTICK_REQ_RESPONSE, contact.id.pub_key, &rr); } -#endif } /* Raw packet logging - sends all RX packets to app for "heard X repeats" etc */ @@ -1418,7 +1399,6 @@ void CompanionMesh::onControlDataRecv(mesh::Packet *packet) return; } -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) if (packet->payload_len >= 6 + PUB_KEY_SIZE && (packet->payload[0] & 0xF0) == CTL_TYPE_NODE_DISCOVER_RESP && (packet->payload[0] & 0x0F) == ADV_TYPE_REPEATER) { @@ -1427,7 +1407,6 @@ void CompanionMesh::onControlDataRecv(mesh::Packet *packet) }; ui_notify_joystick_event(UI_JOYSTICK_DISCOVER_RESP, &packet->payload[6], &dd); } -#endif uint8_t buf[MAX_FRAME_SIZE]; int i = 0; diff --git a/zephcore/app/RepeaterMesh.cpp b/zephcore/app/RepeaterMesh.cpp index f0b8c25..3fd3ed2 100644 --- a/zephcore/app/RepeaterMesh.cpp +++ b/zephcore/app/RepeaterMesh.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -54,7 +53,6 @@ static void uplink_time_sync_cb(uint32_t unix_ts) { if (s_uplink_mesh) { s_uplink_mesh->getRTCClock()->setCurrentTime(unix_ts); - time_sync_report(TIME_SYNC_WIFI); } } #endif diff --git a/zephcore/app/main_observer.cpp b/zephcore/app/main_observer.cpp index e4b6a5f..d3288a1 100644 --- a/zephcore/app/main_observer.cpp +++ b/zephcore/app/main_observer.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #define CLI_REPLY_SIZE 256 @@ -222,7 +221,6 @@ static mesh::ZephyrRTCClock s_rtc_clock; static void time_sync_cb(uint32_t unix_ts) { s_rtc_clock.setCurrentTime(unix_ts); - time_sync_report(TIME_SYNC_WIFI); LOG_INF("RTC synced from SNTP: %u", unix_ts); } diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index 03fef0d..9fcccc4 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -4,7 +4,6 @@ */ #include "CommonCLI.h" -#include #include #include #include @@ -328,7 +327,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch uint32_t curr = getRTCClock()->getCurrentTime(); if (sender_timestamp > curr) { getRTCClock()->setCurrentTime(sender_timestamp + 1); - time_sync_report(TIME_SYNC_CLI); uint32_t now = getRTCClock()->getCurrentTime(); time_t t = (time_t)now; struct tm *tm = gmtime(&t); @@ -348,7 +346,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch uint32_t curr = getRTCClock()->getCurrentTime(); if (secs > curr) { getRTCClock()->setCurrentTime(secs); - time_sync_report(TIME_SYNC_CLI); time_t t = (time_t)secs; struct tm *tm = gmtime(&t); snprintf(reply, CLI_REPLY_SIZE, "OK - clock set: %02d:%02d - %d/%d/%d UTC", diff --git a/zephcore/helpers/joystick_ui_hooks.h b/zephcore/helpers/joystick_ui_hooks.h new file mode 100644 index 0000000..5ecd6db --- /dev/null +++ b/zephcore/helpers/joystick_ui_hooks.h @@ -0,0 +1,112 @@ +/* + * ZephCore - Joystick UI Hooks (public API) + * Copyright (c) 2026 ZephCore + * SPDX-License-Identifier: Apache-2.0 + * + * Mesh layer → joystick UI bridge. Real implementations live in + * helpers/ui-joystick/joystick_ui_hooks.cpp when CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK=y. + * Inline no-ops below keep call sites free of #ifdef when joystick UI is off. + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*ui_signal_fn)(void); + +enum ui_joystick_event_type { + UI_JOYSTICK_LOGIN_RESULT = 0, + UI_JOYSTICK_CLI_RESPONSE = 1, + UI_JOYSTICK_REQ_RESPONSE = 2, + UI_JOYSTICK_DISCOVER_RESP = 3, +}; + +struct ui_joystick_login_data { + bool success; + uint8_t permissions; + uint32_t server_time; +}; + +struct ui_joystick_cli_data { + const char *text; +}; + +struct ui_joystick_req_response_data { + int8_t snr_local; + int8_t snr_remote; + const uint8_t *data; + uint8_t data_len; +}; + +struct ui_joystick_discover_data { + int8_t snr; + int8_t snr_remote; + uint8_t path_len; +}; + +#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) + +void joystick_ui_hooks_register(void *task, + ui_signal_fn signal_refresh, + ui_signal_fn signal_tx); + +void ui_notify_joystick_event(enum ui_joystick_event_type type, + const uint8_t *pub_key_prefix, + const void *payload); + +void ui_notify_radio_stats(uint32_t pkt_recv, uint32_t pkt_sent, uint32_t pkt_errors); + +void ui_signal_refresh(void); +void ui_signal_tx(void); + +bool ui_joystick_try_match_ack(uint32_t ack, uint8_t out_pubkey[6]); + +#else + +static inline void joystick_ui_hooks_register(void *task, + ui_signal_fn signal_refresh, + ui_signal_fn signal_tx) +{ + ARG_UNUSED(task); + ARG_UNUSED(signal_refresh); + ARG_UNUSED(signal_tx); +} + +static inline void ui_notify_joystick_event(enum ui_joystick_event_type type, + const uint8_t *pub_key_prefix, + const void *payload) +{ + ARG_UNUSED(type); + ARG_UNUSED(pub_key_prefix); + ARG_UNUSED(payload); +} + +static inline void ui_notify_radio_stats(uint32_t pkt_recv, uint32_t pkt_sent, + uint32_t pkt_errors) +{ + ARG_UNUSED(pkt_recv); + ARG_UNUSED(pkt_sent); + ARG_UNUSED(pkt_errors); +} + +static inline void ui_signal_refresh(void) {} +static inline void ui_signal_tx(void) {} + +static inline bool ui_joystick_try_match_ack(uint32_t ack, uint8_t out_pubkey[6]) +{ + ARG_UNUSED(ack); + ARG_UNUSED(out_pubkey); + return false; +} + +#endif /* CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK */ + +#ifdef __cplusplus +} +#endif diff --git a/zephcore/helpers/time_sync.h b/zephcore/helpers/time_sync.h index 86c1719..88c9eb5 100644 --- a/zephcore/helpers/time_sync.h +++ b/zephcore/helpers/time_sync.h @@ -1,10 +1,12 @@ /* * SPDX-License-Identifier: Apache-2.0 - * Track which source last set the RTC for UI display. + * Joystick UI: track which source last set the RTC (System > Info > Time). */ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif @@ -17,12 +19,20 @@ enum time_sync_source { TIME_SYNC_CLI, }; -/** Record a successful RTC set from @p src. */ -void time_sync_report(enum time_sync_source src); +#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) -/** Label for System > Info > Time. GPS authority wins while gps_has_time_sync(). */ +void time_sync_report(enum time_sync_source src); const char *time_sync_display_label(void); +#else + +static inline void time_sync_report(enum time_sync_source src) +{ + ARG_UNUSED(src); +} + +#endif /* CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK */ + #ifdef __cplusplus } #endif diff --git a/zephcore/helpers/ui-joystick/joystick_ui_hooks.h b/zephcore/helpers/ui-joystick/joystick_ui_hooks.h index 242e6c5..63d52d4 100644 --- a/zephcore/helpers/ui-joystick/joystick_ui_hooks.h +++ b/zephcore/helpers/ui-joystick/joystick_ui_hooks.h @@ -3,119 +3,8 @@ * Copyright (c) 2026 ZephCore * SPDX-License-Identifier: Apache-2.0 * - * Bridges the ui_task.h C API to JoystickUITask, and declares the - * joystick-only extension API used by CompanionMesh when - * CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK=y. - * - * Non-joystick builds never include this header; guards in CompanionMesh.cpp - * and main_companion.cpp wrap every include and call site. + * Deprecated include path — use (helpers/). */ #pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Signal callback type, passed during registration so joystick_ui_hooks.cpp - * can post events to the mesh loop without depending on main_companion internals. - */ -typedef void (*ui_signal_fn)(void); - -/** - * Register the JoystickUITask instance and event signal callbacks. - * Must be called (from main_companion.cpp) before the mesh event loop starts. - * - * @param task Pointer to JoystickUITask instance - * @param signal_refresh Callback to wake the joystick UI loop tick - * @param signal_tx Callback to wake the mesh TX path immediately - */ -void joystick_ui_hooks_register(void *task, - ui_signal_fn signal_refresh, - ui_signal_fn signal_tx); - -/* - * Joystick UI events: extend by adding a new ui_joystick_event_type value - * and payload struct rather than adding another function. - * Covers ping, login, CLI, discovery, and future joystick features. - * Only compiled in when CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK is enabled. - */ - -enum ui_joystick_event_type { - UI_JOYSTICK_LOGIN_RESULT = 0, /* login accepted/rejected */ - UI_JOYSTICK_CLI_RESPONSE = 1, /* admin CLI command response */ - UI_JOYSTICK_REQ_RESPONSE = 2, /* unmatched contact REQ response, joystick checks for ping */ - UI_JOYSTICK_DISCOVER_RESP = 3, /* CTL_TYPE_NODE_DISCOVER_RESP received */ -}; - -struct ui_joystick_login_data { - bool success; - uint8_t permissions; - uint32_t server_time; -}; - -struct ui_joystick_cli_data { - const char *text; -}; - -struct ui_joystick_req_response_data { - int8_t snr_local; /* SNR of reply as received by us */ - int8_t snr_remote; /* SNR of our REQ as seen by repeater, INT8_MIN if unavailable */ - const uint8_t *data; /* response payload after the 4 byte tag (may be NULL) */ - uint8_t data_len; -}; - -struct ui_joystick_discover_data { - int8_t snr; /* SNR of response as received by us (FROM repeater) */ - int8_t snr_remote; /* SNR of our request as received by repeater (TO repeater) */ - uint8_t path_len; -}; - -/** - * Dispatch a joystick UI event from the mesh layer. - * - * @param type Event discriminator - * @param pub_key_prefix First 4 bytes of the contact's public key - * @param payload Pointer to the matching ui_joystick_*_data struct - */ -void ui_notify_joystick_event(enum ui_joystick_event_type type, - const uint8_t *pub_key_prefix, - const void *payload); - -/** - * Update radio packet counters (Rx, Tx, errors) for the joystick stats screen. - * Not part of ui_task.h, call only when CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK=y. - */ -void ui_notify_radio_stats(uint32_t pkt_recv, uint32_t pkt_sent, uint32_t pkt_errors); - -/** - * Wake the joystick UI loop immediately (before the next render timer fires), - * call after a time sensitive event (ping response, login). - */ -void ui_signal_refresh(void); - -/** - * Wake the mesh TX path immediately so a packet queued by the joystick UI - * (ping, login, CLI command, message) is sent without waiting for the next - * radio event. - */ -void ui_signal_tx(void); - -/** - * Try to match an incoming ACK against the joystick UI's pending-DM table. - * On match: cancels the retry timer, marks the entry delivered, fires the - * deferred BLE-app mirror with the success prefix, and writes the recipient's - * 6-byte pubkey prefix to out_pubkey (caller uses it for BaseChatMesh's - * return-path-retry housekeeping). - * - * @return true if matched (out_pubkey is filled), false otherwise - */ -bool ui_joystick_try_match_ack(uint32_t ack, uint8_t out_pubkey[6]); - -#ifdef __cplusplus -} -#endif +#include "../joystick_ui_hooks.h" diff --git a/zephcore/helpers/time_sync.c b/zephcore/helpers/ui-joystick/time_sync.c similarity index 97% rename from zephcore/helpers/time_sync.c rename to zephcore/helpers/ui-joystick/time_sync.c index a503e89..4b6a41f 100644 --- a/zephcore/helpers/time_sync.c +++ b/zephcore/helpers/ui-joystick/time_sync.c @@ -2,7 +2,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "time_sync.h" +#include #include #include diff --git a/zephcore/helpers/ui/ui_mesh_actions.cpp b/zephcore/helpers/ui/ui_mesh_actions.cpp index 0cde111..9b34f81 100644 --- a/zephcore/helpers/ui/ui_mesh_actions.cpp +++ b/zephcore/helpers/ui/ui_mesh_actions.cpp @@ -27,9 +27,7 @@ LOG_MODULE_REGISTER(zephcore_ui_actions, CONFIG_ZEPHCORE_UI_ACTIONS_LOG_LEVEL); #include #include #include "ui_task.h" -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) #include -#endif #include "ui_mesh_actions.h" /* UI action bit flags — set from input thread, consumed by mesh event loop */ @@ -335,12 +333,10 @@ extern "C" void mesh_housekeeping_ui_refresh(void) s_mesh->prefs.cr, s_mesh->prefs.tx_power_dbm, s_lora_radio->getNoiseFloor()); -#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) ui_notify_radio_stats( s_lora_radio->getPacketsRecv(), s_lora_radio->getPacketsSent(), s_lora_radio->getPacketsRecvErrors()); -#endif /* Update GPS satellite count even without fix */ if (gps_is_enabled()) { diff --git a/zephcore/src/main_repeater.cpp b/zephcore/src/main_repeater.cpp index 64f1500..a1a55b0 100644 --- a/zephcore/src/main_repeater.cpp +++ b/zephcore/src/main_repeater.cpp @@ -41,7 +41,6 @@ extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line) #include #include #include -#include #include /* UI subsystem (display, buttons, buzzer) */ @@ -262,7 +261,6 @@ static void gps_fix_callback(double lat, double lon, int64_t utc_time) if (utc_time > 0) { LOG_INF("GPS fix: RTC sync time=%lld", utc_time); rtc_clock.setCurrentTime((uint32_t)utc_time); - time_sync_report(TIME_SYNC_GPS); } int lat_deg = (int)lat;