From 4f6b0cca277be3da108132ed90547531ae8496dd Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:23:27 +0200 Subject: [PATCH] fix repeater-observer hybrid freezing --- zephcore/adapters/radio/LoRaRadioBase.cpp | 2 +- zephcore/app/ObserverMesh.cpp | 79 ++++++++++++++++++++- zephcore/app/ObserverMesh.h | 3 + zephcore/app/RepeaterMesh.cpp | 83 +++++++++++++++++++++++ zephcore/app/RepeaterMesh.h | 2 + zephcore/app/main_observer.cpp | 16 ++++- zephcore/sysbuild/mcuboot.conf | 2 + 7 files changed, 184 insertions(+), 3 deletions(-) diff --git a/zephcore/adapters/radio/LoRaRadioBase.cpp b/zephcore/adapters/radio/LoRaRadioBase.cpp index 295adfc..4eac9b4 100644 --- a/zephcore/adapters/radio/LoRaRadioBase.cpp +++ b/zephcore/adapters/radio/LoRaRadioBase.cpp @@ -22,6 +22,7 @@ namespace mesh { LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board, NodePrefs *prefs) : _dev(lora_dev), _prefs(prefs), _board(&board), + _loramac_node(false), _in_recv_mode(0), _tx_active(0), _last_rssi(0), _last_snr(0), _rx_head(0), _rx_tail(0), @@ -29,7 +30,6 @@ LoRaRadioBase::LoRaRadioBase(const struct device *lora_dev, MainBoard &board, _rx_duty_cycle_enabled(IS_ENABLED(CONFIG_ZEPHCORE_LORA_RX_DUTY_CYCLE)), _rx_boost_enabled(true), _tx_power_reduction_db(0), - _loramac_node(false), _config_cached(false), _rx_cb(nullptr), _rx_cb_user_data(nullptr), _tx_done_cb(nullptr), _tx_done_cb_user_data(nullptr), diff --git a/zephcore/app/ObserverMesh.cpp b/zephcore/app/ObserverMesh.cpp index 0e28f97..ebb46eb 100644 --- a/zephcore/app/ObserverMesh.cpp +++ b/zephcore/app/ObserverMesh.cpp @@ -39,7 +39,7 @@ namespace mesh { ObserverMesh::ObserverMesh(Radio &radio, MillisecondClock &ms, RNG &rng, RTCClock &rtc) : Dispatcher(radio, ms, _pkt_mgr), _last_rssi(0.0f), _last_score(0.0f), _last_raw_len(0), - _store(nullptr), _creds(nullptr), _rng(&rng), _rtc(&rtc) + _store(nullptr), _creds(nullptr), _rng(&rng), _rtc(&rtc), _start_uptime_secs(0) { memset(_pubkey_hex, 0, sizeof(_pubkey_hex)); memset(_packets_topic, 0, sizeof(_packets_topic)); @@ -52,6 +52,7 @@ void ObserverMesh::begin(RepeaterDataStore *store, struct ObserverCreds *creds) { _store = store; _creds = creds; + _start_uptime_secs = (uint32_t)(k_uptime_get() / 1000); /* Initialize prefs with observer-specific defaults */ initNodePrefs(&_prefs); @@ -92,6 +93,82 @@ void ObserverMesh::begin(RepeaterDataStore *store, struct ObserverCreds *creds) Dispatcher::begin(); } +void ObserverMesh::buildStatusJson(const char *status, char *out, size_t out_size) +{ + uint32_t now_epoch = _rtc ? _rtc->getCurrentTime() : 0; + struct tm tm_now; + time_t t = (time_t)now_epoch; + gmtime_r(&t, &tm_now); + + char ts_buf[48]; + snprintf(ts_buf, sizeof(ts_buf), "%04d-%02d-%02dT%02d:%02d:%02d.000000", + tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday, + tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec); + + char radio_buf[48]; + snprintf(radio_buf, sizeof(radio_buf), "%.3f,%.1f,%u,%u", + (double)_prefs.freq, (double)_prefs.bw, + (unsigned)_prefs.sf, (unsigned)_prefs.cr); + + uint32_t uptime_secs = (uint32_t)(k_uptime_get() / 1000); + if (uptime_secs >= _start_uptime_secs) { + uptime_secs -= _start_uptime_secs; + } else { + uptime_secs = 0; + } + + int noise_floor = ((LoRaRadioBase *)_radio)->getNoiseFloor(); + uint32_t recv_errors = ((LoRaRadioBase *)_radio)->getPacketsRecvErrors(); + + snprintf(out, out_size, + "{" + "\"status\":\"%s\"," + "\"timestamp\":\"%s\"," + "\"origin\":\"%s\"," + "\"origin_id\":\"%s\"," + "\"radio\":\"%s\"," + "\"model\":\"%s\"," + "\"firmware_version\":\"%s\"," + "\"client_version\":\"zephcoretomqtt/1.1\"," + "\"stats\":{" + "\"battery_mv\":%u," + "\"uptime_secs\":%u," + "\"debug_flags\":%u," + "\"queue_len\":%u," + "\"noise_floor\":%d," + "\"tx_air_secs\":%u," + "\"rx_air_secs\":%u," + "\"recv_errors\":%u" + "}" + "}", + status, + ts_buf, + _prefs.node_name, + _pubkey_hex, + radio_buf, +#ifdef CONFIG_ZEPHCORE_BOARD_NAME + CONFIG_ZEPHCORE_BOARD_NAME, +#else + "unknown", +#endif + FIRMWARE_VERSION, + 0u, + uptime_secs, + 0u, + 0u, + noise_floor, + 0u, + 0u, + recv_errors); +} + +void ObserverMesh::publishStatus(const char *status) +{ + static char json_buf[768]; + buildStatusJson(status, json_buf, sizeof(json_buf)); + mqtt_publisher_enqueue(_status_topic, json_buf, strlen(json_buf)); +} + void ObserverMesh::buildTopics() { const char *iata = (_creds && _creds->mqtt_iata[0] != '\0') diff --git a/zephcore/app/ObserverMesh.h b/zephcore/app/ObserverMesh.h index 07a27d0..addf458 100644 --- a/zephcore/app/ObserverMesh.h +++ b/zephcore/app/ObserverMesh.h @@ -53,6 +53,8 @@ class ObserverMesh : public Dispatcher { /* Private helpers */ void buildTopics(); void enqueuePacket(Packet *pkt); + void buildStatusJson(const char *status, char *out, size_t out_size); + uint32_t _start_uptime_secs; protected: /* Capture RSSI + raw bytes before packet is parsed */ @@ -78,6 +80,7 @@ public: * topic so that CoreScope can place it on the map. No-op if lat/lon * are not configured in the creds struct. */ void publishSelfAdvert(); + void publishStatus(const char *status); /* Accessors used by main_observer.cpp */ NodePrefs *getNodePrefs() { return &_prefs; } diff --git a/zephcore/app/RepeaterMesh.cpp b/zephcore/app/RepeaterMesh.cpp index 3b265b3..446e801 100644 --- a/zephcore/app/RepeaterMesh.cpp +++ b/zephcore/app/RepeaterMesh.cpp @@ -841,6 +841,7 @@ RepeaterMesh::RepeaterMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Mil _uplink_last_score = 0.0f; _uplink_last_rssi = 0.0f; _uplink_last_raw_len = 0; + _uplink_next_status_at = 0; #endif } @@ -890,6 +891,12 @@ void RepeaterMesh::begin(RepeaterDataStore* store) { zc_wifi_station_start(&_uplink_creds, uplink_time_sync_cb); mqtt_publisher_start(&_uplink_creds, _prefs.node_name, _uplink_status_topic, _uplink_packets_topic); + mqtt_publisher_set_connect_cb([]() { + if (s_uplink_mesh) { + s_uplink_mesh->publishUplinkStatus("online"); + } + }); + _uplink_next_status_at = futureMillis(300000); LOG_INF("Repeater uplink active: %s", _uplink_packets_topic); } else { LOG_INF("Repeater uplink inactive"); @@ -1430,6 +1437,75 @@ void RepeaterMesh::publishUplinkPacket(mesh::Packet *pkt) } mqtt_publisher_enqueue(_uplink_packets_topic, json_buf, json_len); } + +void RepeaterMesh::publishUplinkStatus(const char *status) +{ + if (!isUplinkEnabled()) return; + if (_uplink_status_topic[0] == '\0') return; + + auto& radio_driver = getRadioDriver(_radio); + uint32_t now_epoch = getRTCClock()->getCurrentTime(); + struct tm tm_now; + time_t t = (time_t)now_epoch; + gmtime_r(&t, &tm_now); + + char ts_buf[48]; + snprintf(ts_buf, sizeof(ts_buf), "%04d-%02d-%02dT%02d:%02d:%02d.000000", + tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday, + tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec); + + char radio_buf[48]; + snprintf(radio_buf, sizeof(radio_buf), "%.3f,%.1f,%u,%u", + (double)_prefs.freq, (double)_prefs.bw, + (unsigned)_prefs.sf, (unsigned)_prefs.cr); + + static char json_buf[768]; + int json_len = snprintf(json_buf, sizeof(json_buf), + "{" + "\"status\":\"%s\"," + "\"timestamp\":\"%s\"," + "\"origin\":\"%s\"," + "\"origin_id\":\"%s\"," + "\"radio\":\"%s\"," + "\"model\":\"%s\"," + "\"firmware_version\":\"%s\"," + "\"client_version\":\"zephcoretomqtt/1.1\"," + "\"stats\":{" + "\"battery_mv\":%u," + "\"uptime_secs\":%u," + "\"debug_flags\":%u," + "\"queue_len\":%u," + "\"noise_floor\":%d," + "\"tx_air_secs\":%u," + "\"rx_air_secs\":%u," + "\"recv_errors\":%u" + "}" + "}", + status, + ts_buf, + _prefs.node_name, + _uplink_pubkey_hex, + radio_buf, +#ifdef CONFIG_ZEPHCORE_BOARD_NAME + CONFIG_ZEPHCORE_BOARD_NAME, +#else + "unknown", +#endif + FIRMWARE_VERSION, + (unsigned)_board.getBattMilliVolts(), + (unsigned)(uptime_millis / 1000), + (unsigned)_err_flags, + (unsigned)_mgr->getOutboundTotal(), + _radio->getNoiseFloor(), + (unsigned)(getTotalAirTime() / 1000), + (unsigned)(getReceiveAirTime() / 1000), + (unsigned)radio_driver.getPacketsRecvErrors()); + + if (json_len <= 0 || json_len >= (int)sizeof(json_buf)) { + return; + } + mqtt_publisher_enqueue(_uplink_status_topic, json_buf, json_len); +} #endif void RepeaterMesh::loop() { @@ -1463,6 +1539,13 @@ void RepeaterMesh::loop() { dirty_contacts_expiry = 0; } +#if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB) + if (_uplink_next_status_at && millisHasNowPassed(_uplink_next_status_at)) { + publishUplinkStatus("online"); + _uplink_next_status_at = futureMillis(300000); + } +#endif + uint32_t now = k_uptime_get(); uptime_millis += now - last_millis; last_millis = now; diff --git a/zephcore/app/RepeaterMesh.h b/zephcore/app/RepeaterMesh.h index 69b4b48..374f172 100644 --- a/zephcore/app/RepeaterMesh.h +++ b/zephcore/app/RepeaterMesh.h @@ -118,6 +118,7 @@ class RepeaterMesh : public mesh::Mesh, public CommonCLICallbacks { float _uplink_last_rssi; uint8_t _uplink_last_raw[MAX_TRANS_UNIT]; int _uplink_last_raw_len; + unsigned long _uplink_next_status_at; #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); @@ -177,6 +178,7 @@ protected: } bool saveUplinkCreds(); void publishUplinkPacket(mesh::Packet *pkt); + void publishUplinkStatus(const char *status); #endif public: diff --git a/zephcore/app/main_observer.cpp b/zephcore/app/main_observer.cpp index 43370c1..2f78646 100644 --- a/zephcore/app/main_observer.cpp +++ b/zephcore/app/main_observer.cpp @@ -43,7 +43,8 @@ static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(LED0_NODE, gpios); #define MESH_EVENT_LORA_RX BIT(0) #define MESH_EVENT_CLI_RX BIT(1) -#define MESH_EVENT_ALL (MESH_EVENT_LORA_RX | MESH_EVENT_CLI_RX) +#define MESH_EVENT_STATUS BIT(2) +#define MESH_EVENT_ALL (MESH_EVENT_LORA_RX | MESH_EVENT_CLI_RX | MESH_EVENT_STATUS) static struct k_event mesh_events; @@ -94,6 +95,14 @@ static void lora_rx_callback(void *user_data) k_event_post(&mesh_events, MESH_EVENT_LORA_RX); } +static void status_timer_fn(struct k_timer *timer) +{ + ARG_UNUSED(timer); + k_event_post(&mesh_events, MESH_EVENT_STATUS); +} + +K_TIMER_DEFINE(status_timer, status_timer_fn, NULL); + /* Observer never transmits — TX done callback not needed */ /* ========== Help banner ========== */ @@ -326,9 +335,11 @@ int main(void) * this observer on the map (requires lat/lon to be configured). */ mqtt_publisher_set_connect_cb([]() { if (s_mesh_ptr) { + s_mesh_ptr->publishStatus("online"); s_mesh_ptr->publishSelfAdvert(); } }); + k_timer_start(&status_timer, K_SECONDS(300), K_SECONDS(300)); LOG_INF("Observer event loop running"); @@ -345,6 +356,9 @@ int main(void) if (ev & MESH_EVENT_CLI_RX) { process_cli_rx(); } + if (ev & MESH_EVENT_STATUS) { + observer_mesh.publishStatus("online"); + } } return 0; diff --git a/zephcore/sysbuild/mcuboot.conf b/zephcore/sysbuild/mcuboot.conf index db1ab4c..7c39c16 100644 --- a/zephcore/sysbuild/mcuboot.conf +++ b/zephcore/sysbuild/mcuboot.conf @@ -36,3 +36,5 @@ CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y # ESP32 HAL's mcuboot_config.h is patched (patches/modules/hal/espressif/...) # to respect Zephyr Kconfig: mbedTLS detection, AUTO sector count, slot0 validation. +CONFIG_BOOT_RSA_PSA=y +CONFIG_BOOT_RSA_TF_PSA_CRYPTO_LEGACY=n