From e115f24b31baa1285443cb298bf326e36fd3e8a2 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 2 Apr 2026 21:06:28 +0200 Subject: [PATCH] buzzer and led queued offline message indicator - buzzer was bugged when message received - green led: hearthbeat, blue led: message received indicator (when not connected to app) - if only one led blinks: hearthbeat 20msec, message received 200msec --- zephcore/app/CompanionMesh.cpp | 20 ++++++++++- zephcore/helpers/ui/ui_task.c | 61 ++++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/zephcore/app/CompanionMesh.cpp b/zephcore/app/CompanionMesh.cpp index 2b2ca7c..fa6198c 100644 --- a/zephcore/app/CompanionMesh.cpp +++ b/zephcore/app/CompanionMesh.cpp @@ -16,6 +16,10 @@ #include #include #include +#if IS_ENABLED(CONFIG_ZEPHCORE_UI_BUTTONS) || IS_ENABLED(CONFIG_ZEPHCORE_UI_BUZZER) || IS_ENABLED(CONFIG_ZEPHCORE_UI_DISPLAY) +#include +#define ZEPHCORE_HAS_UI_TASK 1 +#endif LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL); /* Protocol commands (matches Arduino companion_radio) - sorted by opcode */ @@ -589,7 +593,6 @@ ContactInfo *CompanionMesh::processAck(const uint8_t *data) uint32_t trip_time = now - sent_time; put_le32(&ack_push[4], trip_time); sendPush(PUSH_CODE_SEND_CONFIRMED, ack_push, 8); - return lookupContactByPubKey(ci.id.pub_key, PUB_KEY_SIZE); } } @@ -611,6 +614,10 @@ void CompanionMesh::onMessageRecv(const ContactInfo &contact, mesh::Packet *pkt, markConnectionActive(contact); queueContactMessage(contact, pkt, TXT_TYPE_PLAIN, sender_timestamp, nullptr, 0, text); sendPush(PUSH_CODE_MSG_WAITING); +#if ZEPHCORE_HAS_UI_TASK + ui_set_msg_count((uint16_t)_offline_queue_count); + ui_notify(UI_EVENT_CONTACT_MSG); +#endif } void CompanionMesh::queueContactMessage(const ContactInfo &contact, mesh::Packet *pkt, @@ -685,6 +692,10 @@ void CompanionMesh::onSignedMessageRecv(const ContactInfo &contact, mesh::Packet // sender_prefix is 4 bytes queueContactMessage(contact, pkt, TXT_TYPE_SIGNED_PLAIN, sender_timestamp, sender_prefix, 4, text); sendPush(PUSH_CODE_MSG_WAITING); +#if ZEPHCORE_HAS_UI_TASK + ui_set_msg_count((uint16_t)_offline_queue_count); + ui_notify(UI_EVENT_CONTACT_MSG); +#endif } uint32_t CompanionMesh::calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const @@ -747,6 +758,10 @@ void CompanionMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh LOG_DBG("onChannelMessageRecv: frame_len=%d channel_idx=%d", i, channel_idx); queueOfflineMessage(frame, i); sendPush(PUSH_CODE_MSG_WAITING); +#if ZEPHCORE_HAS_UI_TASK + ui_set_msg_count((uint16_t)_offline_queue_count); + ui_notify(UI_EVENT_CHANNEL_MSG); +#endif } void CompanionMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, @@ -1766,6 +1781,9 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len) if (_sync_pending) { confirmOfflineMessage(); _sync_pending = false; +#if ZEPHCORE_HAS_UI_TASK + ui_set_msg_count((uint16_t)_offline_queue_count); +#endif } uint8_t buf[MAX_FRAME_SIZE]; diff --git a/zephcore/helpers/ui/ui_task.c b/zephcore/helpers/ui/ui_task.c index 45721cb..8a83627 100644 --- a/zephcore/helpers/ui/ui_task.c +++ b/zephcore/helpers/ui/ui_task.c @@ -91,19 +91,28 @@ LOG_MODULE_REGISTER(ui_task, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL); * Works alongside displays; boards that want to disable it can * remove the led0 alias or override this with a Kconfig guard. */ #if DT_NODE_HAS_PROP(DT_ALIAS(led0), gpios) -#define LED_NODE DT_ALIAS(led0) static const struct gpio_dt_spec heartbeat_led = - GPIO_DT_SPEC_GET(LED_NODE, gpios); + GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios); #define HAS_HEARTBEAT_LED 1 #elif DT_NODE_HAS_PROP(DT_ALIAS(led1), gpios) -#define LED_NODE DT_ALIAS(led1) static const struct gpio_dt_spec heartbeat_led = - GPIO_DT_SPEC_GET(LED_NODE, gpios); + GPIO_DT_SPEC_GET(DT_ALIAS(led1), gpios); #define HAS_HEARTBEAT_LED 1 #else #define HAS_HEARTBEAT_LED 0 #endif +/* Message indicator LED — solid ON while offline queue is non-empty. + * Uses led1 only when led0 is already taken by the heartbeat. */ +#if HAS_HEARTBEAT_LED && DT_NODE_HAS_PROP(DT_ALIAS(led0), gpios) && \ + DT_NODE_HAS_PROP(DT_ALIAS(led1), gpios) +static const struct gpio_dt_spec msg_led = + GPIO_DT_SPEC_GET(DT_ALIAS(led1), gpios); +#define HAS_MSG_LED 1 +#else +#define HAS_MSG_LED 0 +#endif + #define LED_CYCLE_MS 4000 /* Total heartbeat period */ #define LED_ON_MS 20 /* Normal pulse width */ #define LED_ON_MSG_MS 200 /* Pulse width when unread messages */ @@ -183,6 +192,9 @@ static void led_off_work_handler(struct k_work *work) { ARG_UNUSED(work); gpio_pin_set_dt(&heartbeat_led, 0); +#if HAS_MSG_LED + gpio_pin_set_dt(&msg_led, 0); +#endif /* Schedule next ON after remainder of cycle */ struct ui_state *s = get_state(); @@ -195,10 +207,16 @@ static void led_on_work_handler(struct k_work *work) { ARG_UNUSED(work); gpio_pin_set_dt(&heartbeat_led, 1); - - /* Schedule OFF after pulse width */ +#if HAS_MSG_LED + struct ui_state *s = get_state(); + if (s->msg_count > 0) { + gpio_pin_set_dt(&msg_led, 1); + } + uint16_t on_ms = (s->msg_count > 0) ? LED_ON_MSG_MS : LED_ON_MS; +#else struct ui_state *s = get_state(); uint16_t on_ms = (s->msg_count > 0) ? LED_ON_MSG_MS : LED_ON_MS; +#endif k_work_reschedule(&led_off_work, K_MSEC(on_ms)); } @@ -550,12 +568,15 @@ static void action_deep_sleep(void) #ifdef CONFIG_POWEROFF LOG_INF("deep sleep: shutting down..."); - /* 1. Stop LED heartbeat */ + /* 1. Stop LED heartbeat and msg indicator */ #if HAS_HEARTBEAT_LED k_work_cancel_delayable(&led_on_work); k_work_cancel_delayable(&led_off_work); gpio_pin_set_dt(&heartbeat_led, 0); #endif +#if HAS_MSG_LED + gpio_pin_set_dt(&msg_led, 0); +#endif /* 2. Play shutdown melody (blocking wait) */ #ifdef CONFIG_ZEPHCORE_UI_BUZZER @@ -860,6 +881,12 @@ int ui_init(void) LOG_INF("LED heartbeat started"); } #endif +#if HAS_MSG_LED + if (gpio_is_ready_dt(&msg_led)) { + gpio_pin_configure_dt(&msg_led, GPIO_OUTPUT_INACTIVE); + LOG_INF("msg LED ready"); + } +#endif /* NOTE: startup chime is NOT played here. It's played from main() * after loadPrefs() so we can respect the persisted buzzer_quiet setting. @@ -938,12 +965,24 @@ void ui_notify(enum ui_event event) break; } - /* Wake display on notifications. - * Repeater: never wake display on events — only user button wakes it. - * Companion: wake unless phone is connected (phone handles its own). */ + /* On message events: flash heartbeat LED immediately (if not BLE connected + * and LEDs are enabled). Cancel the current cycle, turn on now, let + * led_off_work resume the normal heartbeat after LED_ON_MSG_MS. */ +#if HAS_HEARTBEAT_LED + if (is_msg_event && !get_state()->ble_connected && !get_state()->leds_disabled + && gpio_is_ready_dt(&heartbeat_led)) { + k_work_cancel_delayable(&led_on_work); + k_work_cancel_delayable(&led_off_work); + gpio_pin_set_dt(&heartbeat_led, 1); + k_work_reschedule(&led_off_work, K_MSEC(LED_ON_MSG_MS)); + } +#endif + + /* Wake display on non-message notifications (BLE connect/disconnect etc). + * Message notifications use buzzer + LED flash instead of waking the display. */ #ifdef CONFIG_ZEPHCORE_UI_DISPLAY #ifndef ZEPHCORE_REPEATER - if (!(is_msg_event && get_state()->ble_connected)) { + if (!is_msg_event) { mc_display_on(); schedule_render(); }