From 81eca29b69b7fa00a0f4d918b7acbf9c3cbbf008 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:43:10 +1100 Subject: [PATCH] implement meshcore PR 2151 changes --- examples/companion_radio/DataStore.cpp | 10 ++++++ examples/companion_radio/MyMesh.cpp | 42 ++++++++++++++++++++++++++ examples/companion_radio/MyMesh.h | 5 ++- examples/companion_radio/NodePrefs.h | 2 ++ src/Dispatcher.cpp | 33 +++++++++++++++++++- src/Dispatcher.h | 11 ++++++- 6 files changed, 100 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 9e33d5f..3edeaff 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -274,12 +274,20 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no if (file.read((uint8_t *)&_prefs.large_font, sizeof(_prefs.large_font)) != sizeof(_prefs.large_font)) { _prefs.large_font = 0; // default: tiny font } + if (file.read((uint8_t *)&_prefs.tx_fail_reset_threshold, sizeof(_prefs.tx_fail_reset_threshold)) != sizeof(_prefs.tx_fail_reset_threshold)) { + _prefs.tx_fail_reset_threshold = 3; // default: 3 + } + if (file.read((uint8_t *)&_prefs.rx_fail_reboot_threshold, sizeof(_prefs.rx_fail_reboot_threshold)) != sizeof(_prefs.rx_fail_reboot_threshold)) { + _prefs.rx_fail_reboot_threshold = 3; // default: 3 + } // Clamp to valid ranges if (_prefs.dark_mode > 1) _prefs.dark_mode = 0; if (_prefs.portrait_mode > 1) _prefs.portrait_mode = 0; if (_prefs.hint_shown > 1) _prefs.hint_shown = 0; if (_prefs.large_font > 1) _prefs.large_font = 0; + if (_prefs.tx_fail_reset_threshold > 10) _prefs.tx_fail_reset_threshold = 3; + if (_prefs.rx_fail_reboot_threshold > 10) _prefs.rx_fail_reboot_threshold = 3; // auto_lock_minutes: only accept known options (0, 2, 5, 10, 15, 30) { uint8_t alm = _prefs.auto_lock_minutes; @@ -334,6 +342,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.auto_lock_minutes, sizeof(_prefs.auto_lock_minutes)); // 100 file.write((uint8_t *)&_prefs.hint_shown, sizeof(_prefs.hint_shown)); // 101 file.write((uint8_t *)&_prefs.large_font, sizeof(_prefs.large_font)); // 102 + file.write((uint8_t *)&_prefs.tx_fail_reset_threshold, sizeof(_prefs.tx_fail_reset_threshold)); // 103 + file.write((uint8_t *)&_prefs.rx_fail_reboot_threshold, sizeof(_prefs.rx_fail_reboot_threshold)); // 104 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index f6ee1a8..b1417f8 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -264,6 +264,16 @@ int MyMesh::getInterferenceThreshold() const { return _prefs.interference_threshold; } +uint8_t MyMesh::getTxFailResetThreshold() const { + return _prefs.tx_fail_reset_threshold; +} +uint8_t MyMesh::getRxFailRebootThreshold() const { + return _prefs.rx_fail_reboot_threshold; +} +void MyMesh::onRxUnrecoverable() { + board.reboot(); +} + int MyMesh::calcRxDelay(float score, uint32_t air_time) const { if (_prefs.rx_delay_base <= 0.0f) return 0; return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); @@ -2255,6 +2265,10 @@ void MyMesh::checkCLIRescueCmd() { Serial.printf(" > %d\n", _prefs.multi_acks); } else if (strcmp(key, "int.thresh") == 0) { Serial.printf(" > %d\n", _prefs.interference_threshold); + } else if (strcmp(key, "tx.fail.threshold") == 0) { + Serial.printf(" > %d\n", _prefs.tx_fail_reset_threshold); + } else if (strcmp(key, "rx.fail.threshold") == 0) { + Serial.printf(" > %d\n", _prefs.rx_fail_reboot_threshold); } else if (strcmp(key, "gps.baud") == 0) { uint32_t effective = _prefs.gps_baudrate ? _prefs.gps_baudrate : GPS_BAUDRATE; Serial.printf(" > %lu (effective: %lu)\n", @@ -2315,6 +2329,8 @@ void MyMesh::checkCLIRescueCmd() { Serial.printf(" af: %.1f\n", _prefs.airtime_factor); Serial.printf(" multi.acks: %d\n", _prefs.multi_acks); Serial.printf(" int.thresh: %d\n", _prefs.interference_threshold); + Serial.printf(" tx.fail: %d\n", _prefs.tx_fail_reset_threshold); + Serial.printf(" rx.fail: %d\n", _prefs.rx_fail_reboot_threshold); { uint32_t eff_baud = _prefs.gps_baudrate ? _prefs.gps_baudrate : GPS_BAUDRATE; Serial.printf(" gps.baud: %lu\n", (unsigned long)eff_baud); @@ -2710,6 +2726,30 @@ void MyMesh::checkCLIRescueCmd() { Serial.println(" Error: use 0 (disabled) or 14+ (typical: 14)"); } + } else if (memcmp(config, "tx.fail.threshold ", 18) == 0) { + int val = atoi(&config[18]); + if (val < 0) val = 0; + if (val > 10) val = 10; + _prefs.tx_fail_reset_threshold = (uint8_t)val; + savePrefs(); + if (val == 0) { + Serial.println(" > tx fail reset disabled"); + } else { + Serial.printf(" > tx fail reset after %d failures\n", val); + } + + } else if (memcmp(config, "rx.fail.threshold ", 18) == 0) { + int val = atoi(&config[18]); + if (val < 0) val = 0; + if (val > 10) val = 10; + _prefs.rx_fail_reboot_threshold = (uint8_t)val; + savePrefs(); + if (val == 0) { + Serial.println(" > rx fail reboot disabled"); + } else { + Serial.printf(" > reboot after %d rx recovery failures\n", val); + } + } else if (memcmp(config, "gps.baud ", 9) == 0) { uint32_t val = (uint32_t)atol(&config[9]); if (val == 0 || val == 4800 || val == 9600 || val == 19200 || @@ -2807,6 +2847,8 @@ void MyMesh::checkCLIRescueCmd() { Serial.println(" af <0-9> Airtime factor"); Serial.println(" multi.acks <0|1> Redundant ACKs (default: 1)"); Serial.println(" int.thresh <0|14+> Interference threshold dB (0=off, 14=typical)"); + Serial.println(" tx.fail.threshold <0-10> TX fail radio reset (0=off, default 3)"); + Serial.println(" rx.fail.threshold <0-10> RX stuck reboot (0=off, default 3)"); Serial.println(" gps.baud GPS baud (0=default, reboot to apply)"); Serial.println(""); Serial.println(" Clock:"); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index b25a1bc..a082116 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,7 +8,7 @@ #define FIRMWARE_VER_CODE 10 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "26 March 2026" +#define FIRMWARE_BUILD_DATE "27 March 2026" #endif #ifndef FIRMWARE_VERSION @@ -143,6 +143,9 @@ public: protected: float getAirtimeBudgetFactor() const override; int getInterferenceThreshold() const override; + uint8_t getTxFailResetThreshold() const override; + uint8_t getRxFailRebootThreshold() const override; + void onRxUnrecoverable() override; int calcRxDelay(float score, uint32_t air_time) const override; uint32_t getRetransmitDelay(const mesh::Packet *packet) override; uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 55ae1f8..79b9982 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -40,6 +40,8 @@ struct NodePrefs { // persisted to file uint8_t auto_lock_minutes; // 0=disabled, 2/5/10/15/30=auto-lock after idle uint8_t hint_shown; // 0=show nav hint on boot, 1=already shown (dismiss permanently) uint8_t large_font; // 0=tiny (built-in 6x8), 1=larger (FreeSans9pt) — T-Deck Pro only + uint8_t tx_fail_reset_threshold; // 0=disabled, 1-10, default 3 + uint8_t rx_fail_reboot_threshold; // 0=disabled, 1-10, default 3 // --- Font helpers (inline, no overhead) --- // Returns the DisplayDriver text-size index for "small/body" text. diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 273ff8f..4ae8421 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -52,10 +52,28 @@ void Dispatcher::loop() { prev_isrecv_mode = is_recv; if (!is_recv) { radio_nonrx_start = _ms->getMillis(); + } else { + rx_stuck_count = 0; // radio recovered — reset counter } } if (!is_recv && _ms->getMillis() - radio_nonrx_start > 8000) { // radio has not been in Rx mode for 8 seconds! _err_flags |= ERR_EVENT_STARTRX_TIMEOUT; + + rx_stuck_count++; + MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): RX stuck (attempt %d), calling onRxStuck()", getLogDateTime(), rx_stuck_count); + onRxStuck(); + + uint8_t reboot_threshold = getRxFailRebootThreshold(); + if (reboot_threshold > 0 && rx_stuck_count >= reboot_threshold) { + MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): RX unrecoverable after %d attempts", getLogDateTime(), rx_stuck_count); + onRxUnrecoverable(); + } + + // Reset state to give recovery the full 8s window before re-triggering + radio_nonrx_start = _ms->getMillis(); + prev_isrecv_mode = true; + cad_busy_start = 0; + next_agc_reset_time = futureMillis(getAGCResetInterval()); } if (outbound) { // waiting for outbound send to be completed @@ -277,14 +295,27 @@ void Dispatcher::checkSend() { logTxFail(outbound, outbound->getRawLength()); - // re-queue instead of dropping so the packet gets another chance + // re-queue packet for retry instead of dropping it int retry_delay = getCADFailRetryDelay(); unsigned long retry_time = futureMillis(retry_delay); _mgr->queueOutbound(outbound, 0, retry_time); outbound = NULL; next_tx_time = retry_time; + + // count consecutive failures and reset radio if stuck + uint8_t threshold = getTxFailResetThreshold(); + if (threshold > 0) { + tx_fail_count++; + if (tx_fail_count >= threshold) { + MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): TX stuck (%d failures), resetting radio", getLogDateTime(), tx_fail_count); + onTxStuck(); + tx_fail_count = 0; + next_tx_time = futureMillis(2000); + } + } return; } + tx_fail_count = 0; // clear counter on successful TX start outbound_expiry = futureMillis(max_airtime); #if MESH_PACKET_LOGGING diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 25a41d8..d2bac75 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -122,6 +122,8 @@ class Dispatcher { bool prev_isrecv_mode; uint32_t n_sent_flood, n_sent_direct; uint32_t n_recv_flood, n_recv_direct; + uint8_t tx_fail_count; + uint8_t rx_stuck_count; void processRecvPacket(Packet* pkt); @@ -142,6 +144,8 @@ protected: _err_flags = 0; radio_nonrx_start = 0; prev_isrecv_mode = true; + tx_fail_count = 0; + rx_stuck_count = 0; } virtual DispatcherAction onRecvPacket(Packet* pkt) = 0; @@ -159,6 +163,11 @@ protected: virtual uint32_t getCADFailMaxDuration() const; virtual int getInterferenceThreshold() const { return 0; } // disabled by default virtual int getAGCResetInterval() const { return 0; } // disabled by default + virtual uint8_t getTxFailResetThreshold() const { return 3; } // reset radio after N consecutive TX failures; 0=disabled + virtual void onTxStuck() { _radio->resetAGC(); } // override to use doFullRadioReset() when available + virtual uint8_t getRxFailRebootThreshold() const { return 3; } // reboot after N failed RX recovery attempts; 0=disabled + virtual void onRxStuck() { _radio->resetAGC(); } // called each time RX stuck for 8s; override for deeper reset + virtual void onRxUnrecoverable() { } // called when reboot threshold exceeded; override to call _board->reboot() public: void begin(); @@ -188,4 +197,4 @@ private: void checkSend(); }; -} +} \ No newline at end of file