From 9c9a2ba2db593b203eb66a428fd21f9b26a7d24e Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Mon, 23 Feb 2026 21:40:17 +0100 Subject: [PATCH 01/45] Fix stack buffer overflow in MACThenDecrypt via block-alignment check Add validation that ciphertext length is a multiple of CIPHER_BLOCK_SIZE before calling decrypt(). The decrypt() function processes data in 16-byte AES blocks, rounding up non-aligned input, which can write up to 8 bytes past the caller's stack buffer. This can be triggered by crafted packets that pass the 2-byte HMAC check (1-in-65536 chance per attempt), enabling a persistent denial-of-service attack against repeaters and other nodes. --- src/Utils.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index 186c8720a..573c4398f 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -82,7 +82,9 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac, CIPHER_MAC_SIZE); } if (memcmp(hmac, src, CIPHER_MAC_SIZE) == 0) { - return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE); + int enc_len = src_len - CIPHER_MAC_SIZE; + if (enc_len % CIPHER_BLOCK_SIZE != 0) return 0; // reject non-block-aligned ciphertext + return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, enc_len); } return 0; // invalid HMAC } From b74ba545308f077120cf329f2023f171e2e2f9fa Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Thu, 26 Feb 2026 09:08:06 +0100 Subject: [PATCH 02/45] Move block-alignment check before HMAC computation Address review feedback: - Move the alignment check before the HMAC-SHA256 computation to skip expensive crypto on obviously malformed packets - Update MACThenDecrypt header comment to document block-alignment as a reason for returning zero --- src/Utils.cpp | 7 ++++--- src/Utils.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index 573c4398f..a70d4a70c 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -74,16 +74,17 @@ int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uin int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { if (src_len <= CIPHER_MAC_SIZE) return 0; // invalid src bytes + int enc_len = src_len - CIPHER_MAC_SIZE; + if (enc_len % CIPHER_BLOCK_SIZE != 0) return 0; // reject non-block-aligned ciphertext + uint8_t hmac[CIPHER_MAC_SIZE]; { SHA256 sha; sha.resetHMAC(shared_secret, PUB_KEY_SIZE); - sha.update(src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE); + sha.update(src + CIPHER_MAC_SIZE, enc_len); sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac, CIPHER_MAC_SIZE); } if (memcmp(hmac, src, CIPHER_MAC_SIZE) == 0) { - int enc_len = src_len - CIPHER_MAC_SIZE; - if (enc_len % CIPHER_BLOCK_SIZE != 0) return 0; // reject non-block-aligned ciphertext return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, enc_len); } return 0; // invalid HMAC diff --git a/src/Utils.h b/src/Utils.h index 5736b8747..9997fe3cb 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -50,7 +50,7 @@ public: /** * \brief checks the MAC (in leading bytes of 'src'), then if valid, decrypts remaining bytes in src. - * \returns zero if MAC is invalid, otherwise the length of decrypted bytes in 'dest' + * \returns zero if MAC is invalid or ciphertext is not block-aligned, otherwise the length of decrypted bytes in 'dest' */ static int MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len); From e525200ce1f88044167d1431719c9ebc5a69ecfd Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Tue, 17 Mar 2026 00:50:19 -0500 Subject: [PATCH 03/45] Fix T1000-E sensor power pin mapping --- variants/t1000-e/T1000eBoard.h | 4 ---- variants/t1000-e/t1000e_sensors.cpp | 4 ---- variants/t1000-e/variant.cpp | 4 +--- variants/t1000-e/variant.h | 3 +-- 4 files changed, 2 insertions(+), 13 deletions(-) diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 492236077..546bd774d 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -69,10 +69,6 @@ public: #ifdef PIN_3V3_ACC_EN digitalWrite(PIN_3V3_ACC_EN, LOW); #endif - #ifdef SENSOR_EN - digitalWrite(SENSOR_EN, LOW); - #endif - // set led on and wait for button release before poweroff #ifdef LED_PIN digitalWrite(LED_PIN, HIGH); diff --git a/variants/t1000-e/t1000e_sensors.cpp b/variants/t1000-e/t1000e_sensors.cpp index 85298d3a8..cb4977fd4 100644 --- a/variants/t1000-e/t1000e_sensors.cpp +++ b/variants/t1000-e/t1000e_sensors.cpp @@ -72,7 +72,6 @@ float t1000e_get_temperature(void) { unsigned int ntc_v, vcc_v; digitalWrite(PIN_3V3_EN, HIGH); - digitalWrite(SENSOR_EN, HIGH); analogReference(AR_INTERNAL_3_0); analogReadResolution(12); delay(10); @@ -80,7 +79,6 @@ float t1000e_get_temperature(void) { vcc_v = (rail_v > NTC_REF_VCC) ? NTC_REF_VCC : rail_v; ntc_v = (1000.0 * AREF_VOLTAGE * analogRead(TEMP_SENSOR)) / 4096; digitalWrite(PIN_3V3_EN, LOW); - digitalWrite(SENSOR_EN, LOW); return get_heater_temperature(vcc_v, ntc_v); } @@ -90,13 +88,11 @@ uint32_t t1000e_get_light(void) { unsigned int lux_v = 0; digitalWrite(PIN_3V3_EN, HIGH); - digitalWrite(SENSOR_EN, HIGH); analogReference(AR_INTERNAL_3_0); analogReadResolution(12); delay(10); lux_v = 1000 * analogRead(LUX_SENSOR) * AREF_VOLTAGE / 4096; lux = get_light_lv(lux_v); - digitalWrite(SENSOR_EN, LOW); digitalWrite(PIN_3V3_EN, LOW); return lux; diff --git a/variants/t1000-e/variant.cpp b/variants/t1000-e/variant.cpp index a598e3cad..5627e6599 100644 --- a/variants/t1000-e/variant.cpp +++ b/variants/t1000-e/variant.cpp @@ -14,7 +14,7 @@ const uint32_t g_ADigitalPinMap[PINS_COUNT + 1] = 1, // P0.01 2, // P0.02, AIN0 BATTERY_PIN 3, // P0.03 - 4, // P0.04, SENSOR_EN + 4, // P0.04 5, // P0.05, EXT_PWR_DETEC 6, // P0.06, PIN_BUTTON1 7, // P0.07, LORA_BUSY @@ -77,7 +77,6 @@ void initVariant() pinMode(PIN_3V3_EN, OUTPUT); pinMode(PIN_3V3_ACC_EN, OUTPUT); pinMode(BUZZER_EN, OUTPUT); - pinMode(SENSOR_EN, OUTPUT); pinMode(GPS_EN, OUTPUT); pinMode(GPS_RESET, OUTPUT); pinMode(GPS_VRTC_EN, OUTPUT); @@ -88,7 +87,6 @@ void initVariant() digitalWrite(PIN_3V3_EN, LOW); digitalWrite(PIN_3V3_ACC_EN, LOW); digitalWrite(BUZZER_EN, LOW); - digitalWrite(SENSOR_EN, LOW); digitalWrite(GPS_EN, LOW); digitalWrite(GPS_RESET, LOW); digitalWrite(GPS_VRTC_EN, LOW); diff --git a/variants/t1000-e/variant.h b/variants/t1000-e/variant.h index 458f12bcc..81e028d61 100644 --- a/variants/t1000-e/variant.h +++ b/variants/t1000-e/variant.h @@ -120,7 +120,6 @@ //////////////////////////////////////////////////////////////////////////////// // Temp+Lux Sensor -#define SENSOR_EN (4) // P0.4 #define TEMP_SENSOR (31) // P0.31/AIN7 #define LUX_SENSOR (29) // P0.29/AIN5 @@ -133,4 +132,4 @@ // Buzzer #define BUZZER_EN (37) // P1.5 -#define BUZZER_PIN (25) // P0.25 \ No newline at end of file +#define BUZZER_PIN (25) // P0.25 From fc71d34509edf593dfd12622d8c0c8ac2394107d Mon Sep 17 00:00:00 2001 From: Emily Kerst Date: Sun, 3 May 2026 22:40:01 +0200 Subject: [PATCH 04/45] Added USB Companion variant for LilyGo T-Echo Lite Co-authored-by: Natalie Kagelmacher Mentored-by: Natalie Kagelmacher Tested-by: Emily Kerst --- variants/lilygo_techo_lite/platformio.ini | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index 4b5edf691..db629b363 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -190,3 +190,24 @@ lib_deps = extends = LilyGo_T-Echo-Lite build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} +<../examples/kiss_modem/> +[env:LilyGo_T-Echo-Lite_companion_radio_usb] +extends = LilyGo_T-Echo-Lite +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${LilyGo_T-Echo-Lite.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +; -D QSPIFLASH=1 +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_T-Echo-Lite.lib_deps} + densaugeo/base64 @ ~1.4.0 From 356af9fdc8ff60a2cd60432b6aae2162f6ed07a0 Mon Sep 17 00:00:00 2001 From: Emily Kerst Date: Fri, 15 May 2026 20:20:27 +0200 Subject: [PATCH 05/45] chore: reformat T-Echo Lite platformio.ini to keep normal and non-shell variants together --- variants/lilygo_techo_lite/platformio.ini | 52 +++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index db629b363..ed46adcd6 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -98,6 +98,32 @@ lib_deps = ${LilyGo_T-Echo-Lite.lib_deps} densaugeo/base64 @ ~1.4.0 +[env:LilyGo_T-Echo-Lite_kiss_modem] +extends = LilyGo_T-Echo-Lite +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + +<../examples/kiss_modem/> +[env:LilyGo_T-Echo-Lite_companion_radio_usb] +extends = LilyGo_T-Echo-Lite +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${LilyGo_T-Echo-Lite.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +; -D QSPIFLASH=1 +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_T-Echo-Lite.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:LilyGo_T-Echo-Lite_non_shell_companion_radio_ble] extends = LilyGo_T-Echo-Lite upload_protocol = nrfutil @@ -185,29 +211,3 @@ build_src_filter = ${nrf52_base.build_src_filter} lib_deps = ${LilyGo_T-Echo-Lite.lib_deps} densaugeo/base64 @ ~1.4.0 - -[env:LilyGo_T-Echo-Lite_kiss_modem] -extends = LilyGo_T-Echo-Lite -build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} - +<../examples/kiss_modem/> -[env:LilyGo_T-Echo-Lite_companion_radio_usb] -extends = LilyGo_T-Echo-Lite -board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld -board_upload.maximum_size = 712704 -build_flags = - ${LilyGo_T-Echo-Lite.build_flags} - -I src/helpers/ui - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D OFFLINE_QUEUE_SIZE=256 - -D UI_RECENT_LIST_SIZE=9 - -D UI_SENSORS_PAGE=1 - -D AUTO_SHUTDOWN_MILLIVOLTS=3300 -; -D QSPIFLASH=1 -build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${LilyGo_T-Echo-Lite.lib_deps} - densaugeo/base64 @ ~1.4.0 From 27feb73d4ebf8ca4107f3b58bab854a8016bfa24 Mon Sep 17 00:00:00 2001 From: Jody Bentley Date: Wed, 24 Jun 2026 19:43:55 -0400 Subject: [PATCH 06/45] GPS: poll up to 5s for cold-start detection instead of a single 1s check initBasicGPS() waited a fixed 1 second and then decided the GPS was present only if NMEA data had already arrived. Cold-start GPS modules often need 2-5 seconds before they emit their first sentence, so on a cold boot detection frequently failed. A failed detection sets gps_detected = false, which hides all GPS CLI settings and disables GPS time sync until the next reboot - so the GPS appears permanently dead even though the hardware is fine. Poll for up to ~5 seconds (in 250ms steps) and return as soon as any data appears. Fast modules are detected almost immediately and add no boot delay; slow cold-start modules now get enough time. The ENV_SKIP_GPS_DETECT bypass is unchanged. Fixes #2258. --- src/helpers/sensors/EnvironmentSensorManager.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 73842d9ee..b26fc0380 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -748,14 +748,18 @@ void EnvironmentSensorManager::initBasicGPS() { MESH_DEBUG_PRINTLN("No GPS wake/reset pin found for this board. Continuing on..."); #endif - // Give GPS a moment to power up and send data - delay(1000); - - // We'll consider GPS detected if we see any data on Serial1 + // Give the GPS time to power up and emit its first NMEA sentence. Cold-start + // modules can take several seconds, so poll for up to ~5s instead of checking + // once after a fixed 1s delay -- returning as soon as any data appears, so + // fast modules add no boot delay (issue #2258). #ifdef ENV_SKIP_GPS_DETECT gps_detected = true; #else - gps_detected = (Serial1.available() > 0); + gps_detected = false; + for (int i = 0; i < 20 && !gps_detected; i++) { + delay(250); + gps_detected = (Serial1.available() > 0); + } #endif if (gps_detected) { From 4866268cb9fabfcfdb406ff86521badfb841d497 Mon Sep 17 00:00:00 2001 From: me Date: Sun, 7 Jun 2026 18:11:23 -0700 Subject: [PATCH 07/45] fix(techo-lite): remove duplicate EXTERNAL_FLASH_DEVICES definition variant.h defines EXTERNAL_FLASH_DEVICES twice: - First (correct): ZD25WQ32CEIGR (32Mbit = 4MB, matches actual hardware) - Second (incorrect): MX25R1635F (16Mbit = 2MB, T-Echo non-Lite chip) Due to C preprocessor behavior, the second definition silently overrides the first, causing firmware to initialize the wrong flash chip. This can result in LittleFS operating incorrectly on T-Echo Lite hardware. Remove the erroneous second definition. Also fix missing newline at end of file. --- variants/lilygo_techo_lite/variant.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index 702ab3349..628ffe197 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -95,9 +95,6 @@ #define PIN_BUTTON2 _PINNUM(0, 18) #define BUTTON_PIN2 PIN_BUTTON2 -#define EXTERNAL_FLASH_DEVICES MX25R1635F -#define EXTERNAL_FLASH_USE_QSPI - //////////////////////////////////////////////////////////////////////////////// // Lora @@ -156,4 +153,4 @@ extern const int SCK; #define PIN_GPS_RX _PINNUM(1, 10) // GPS UART RX ← MCU TX #define GPS_EN _PINNUM(1, 11) // GPS RT9080 power enable #define PIN_GPS_STANDBY _PINNUM(1, 13) // GPS wake-up -#define PIN_GPS_PPS _PINNUM(1, 15) // GPS 1PPS \ No newline at end of file +#define PIN_GPS_PPS _PINNUM(1, 15) // GPS 1PPS From 5de6512090f657dca2c717347ce1cbf516fb6f77 Mon Sep 17 00:00:00 2001 From: me Date: Thu, 2 Jul 2026 08:43:06 -0700 Subject: [PATCH 08/45] fix(techo-lite): remove unused EXTERNAL_FLASH_DEVICES definition entirely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review feedback from @liamcottle and @entr0p1, EXTERNAL_FLASH_DEVICES is not used by MeshCore firmware — CustomLFS determines the flash chip via JEDEC ID at runtime instead. Removing the definition entirely rather than keeping the (now-confirmed-unnecessary) correct value. Also removes EXTERNAL_FLASH_USE_QSPI, which was paired with the same define. --- variants/lilygo_techo_lite/variant.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index 628ffe197..673e8fc7d 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -70,9 +70,6 @@ #define PIN_QSPI_IO2 _PINNUM(1, 9) #define PIN_QSPI_IO3 _PINNUM(0, 26) -#define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR -#define EXTERNAL_FLASH_USE_QSPI - //////////////////////////////////////////////////////////////////////////////// // Builtin LEDs From 67902026098b0fec8a3a8305d4d37493fdb4aab4 Mon Sep 17 00:00:00 2001 From: Szetya Date: Mon, 20 Jul 2026 14:11:06 +0200 Subject: [PATCH 09/45] Update variant.h Correct of GPS RX/TX Pins. --- variants/lilygo_techo_lite/variant.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index 702ab3349..19b12b1b4 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -152,8 +152,8 @@ extern const int SCK; // GPS — per LilyGo t_echo_lite_config.h // PIN_GPS_TX/RX named from GPS module's perspective -#define PIN_GPS_TX _PINNUM(0, 29) // GPS UART TX → MCU RX -#define PIN_GPS_RX _PINNUM(1, 10) // GPS UART RX ← MCU TX +#define PIN_GPS_RX _PINNUM(0, 29) // GPS UART TX → MCU RX +#define PIN_GPS_TX _PINNUM(1, 10) // GPS UART RX ← MCU TX #define GPS_EN _PINNUM(1, 11) // GPS RT9080 power enable #define PIN_GPS_STANDBY _PINNUM(1, 13) // GPS wake-up -#define PIN_GPS_PPS _PINNUM(1, 15) // GPS 1PPS \ No newline at end of file +#define PIN_GPS_PPS _PINNUM(1, 15) // GPS 1PPS From fe72d0321a30397e390c6c21cb6a79ce11de54de Mon Sep 17 00:00:00 2001 From: Bill Plein <260078+bplein@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:51:01 -0500 Subject: [PATCH 10/45] Allow for build-time macro to disable the bluetooth LED Several variants use one or another method to disable the bluetooth LED that is grabbed by the Adafruit Bluefruit library by default. This allows for single build_flags macro to control the behavior (default behavior if not defined, not auto-controlled by Bluefruit if defined). This would open up for future cases where this could be controlled via a CLI command or by an application. Does not change behavior if the flag is not defined. --- src/helpers/nrf52/SerialBLEInterface.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 75a4e3b06..2e5626a8d 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -129,6 +129,12 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code char charpin[20]; snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code); + #ifdef DISABLE_BLE_LED \\ use variant platformio.ini build_flags macro: -D DISABLE_BLE_LED + Bluefruit.autoConnLed(false); \\ false removes the automatic control of the LED by the Bluefruit library + #else + Bluefruit.autoConnLed(true); \\ true leaves the default automatic control of the LED by the Bluefruit library + #endif + // If we want to control BLE LED ourselves, uncomment this: // Bluefruit.autoConnLed(false); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); From fc26cae198cbfb6028bc9c53c52e93bd2afecd07 Mon Sep 17 00:00:00 2001 From: F4FPR Date: Thu, 13 Aug 2026 23:52:02 +0200 Subject: [PATCH 11/45] Update LocationProvider.h --- src/helpers/sensors/LocationProvider.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index 81d08652e..a70ae8ca6 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -1,6 +1,8 @@ #pragma once #include "Mesh.h" +#include +#include class LocationProvider { @@ -22,4 +24,14 @@ public: virtual void stop() = 0; virtual void loop() = 0; virtual bool isEnabled() = 0; + + // Format compact diagnostics in the caller-provided buffer. Providers that + // have more information (for example UART counters) can override this. + virtual void formatDiagnostics(char* out, size_t out_size) { + if (out_size == 0) return; + snprintf(out, out_size, "en:%u sat:%ld fix:%u", + isEnabled() ? 1U : 0U, + satellitesCount(), + isValid() ? 1U : 0U); + } }; From dcd601135e9a4439bcb647c29efaae023ba8ac23 Mon Sep 17 00:00:00 2001 From: F4FPR Date: Thu, 13 Aug 2026 23:52:31 +0200 Subject: [PATCH 12/45] Update MicroNMEALocationProvider.h --- .../sensors/MicroNMEALocationProvider.h | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index b66cb1145..0a6ebc1da 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -4,6 +4,7 @@ #include #include #include +#include #ifndef GPS_EN #ifdef PIN_GPS_EN @@ -51,6 +52,32 @@ class MicroNMEALocationProvider : public LocationProvider { unsigned long _last_time_sync = 0; static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes + // Observation-only diagnostics. These fields do not change GPS power, + // timing, reset behaviour, or parser input. + uint32_t _uart_bytes = 0; + uint32_t _nmea_checksum_ok = 0; + uint32_t _nmea_checksum_bad = 0; + uint32_t _begin_calls = 0; + uint32_t _stop_calls = 0; + unsigned long _last_uart_ms = 0; + unsigned long _last_nmea_ms = 0; + unsigned long _last_fix_ms = 0; + bool _uart_seen = false; + bool _nmea_seen = false; + bool _fix_seen = false; + + static uint32_t ageMs(unsigned long timestamp) { + return (uint32_t)(millis() - timestamp); + } + + static void formatAge(char* out, size_t out_size, bool seen, unsigned long timestamp) { + if (!seen) { + snprintf(out, out_size, "never"); + } else { + snprintf(out, out_size, "%lu", (unsigned long)ageMs(timestamp)); + } + } + public : MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _gps_serial(&ser), _peripher_power(peripher_power), _pin_reset(pin_reset), _pin_en(pin_en) { @@ -76,6 +103,7 @@ public : } void begin() override { + _begin_calls++; claim(); if (_pin_en != -1) { digitalWrite(_pin_en, GPS_EN_ACTIVE); @@ -94,6 +122,7 @@ public : } void stop() override { + _stop_calls++; if (_pin_en != -1) { digitalWrite(_pin_en, !GPS_EN_ACTIVE); } @@ -137,10 +166,36 @@ public : while (_gps_serial->available()) { char c = _gps_serial->read(); + _uart_bytes++; + _last_uart_ms = millis(); + _uart_seen = true; #ifdef GPS_NMEA_DEBUG Serial.print(c); #endif - nmea.process(c); + bool parsed = nmea.process(c); + + // MicroNMEA leaves the completed sentence in its buffer after the + // first line terminator. CRLF therefore counts once: CR observes the + // sentence, then LF clears the now-empty buffer. + if ((c == '\0' || c == '\r' || c == '\n') && nmea.getSentence()[0] != '\0') { + if (MicroNMEA::testChecksum(nmea.getSentence())) { + _nmea_checksum_ok++; + _last_nmea_ms = millis(); + _nmea_seen = true; + + // Refresh fix age only when a newly parsed GGA/RMC sentence + // carries a valid fix. Other valid NMEA sentences must not + // make an old position look fresh. + const char* message_id = nmea.getMessageID(); + if (parsed && isValid() && + (strcmp(message_id, "GGA") == 0 || strcmp(message_id, "RMC") == 0)) { + _last_fix_ms = millis(); + _fix_seen = true; + } + } else { + _nmea_checksum_bad++; + } + } } if (!isValid()) time_valid = 0; @@ -163,4 +218,31 @@ public : } } } + + void formatDiagnostics(char* out, size_t out_size) override { + if (out_size == 0) return; + + char uart_age[11]; + char nmea_age[11]; + char fix_age[11]; + formatAge(uart_age, sizeof(uart_age), _uart_seen, _last_uart_ms); + formatAge(nmea_age, sizeof(nmea_age), _nmea_seen, _last_nmea_ms); + formatAge(fix_age, sizeof(fix_age), _fix_seen, _last_fix_ms); + + // Compact enough for CommonCLI's 160-byte reply buffer, including the + // "req:" prefix added by CommonCLI. + snprintf(out, out_size, + "en:%u ub:%lu ua:%s ok:%lu bad:%lu na:%s sat:%ld fix:%u fa:%s bc:%lu sc:%lu", + isEnabled() ? 1U : 0U, + (unsigned long)_uart_bytes, + uart_age, + (unsigned long)_nmea_checksum_ok, + (unsigned long)_nmea_checksum_bad, + nmea_age, + satellitesCount(), + isValid() ? 1U : 0U, + fix_age, + (unsigned long)_begin_calls, + (unsigned long)_stop_calls); + } }; From f110ce735c639425e1eef8c5f65906e0d44a03d2 Mon Sep 17 00:00:00 2001 From: F4FPR Date: Thu, 13 Aug 2026 23:52:58 +0200 Subject: [PATCH 13/45] Update CommonCLI.cpp --- src/helpers/CommonCLI.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b318bb58e..b8fd6d567 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -344,6 +344,20 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } else { strcpy(reply, "gps provider not found"); } + } else if (strcmp(command, "gps diag") == 0) { + LocationProvider * l = _sensors->getLocationProvider(); + if (l != NULL) { + const char* setting = _sensors->getSettingByKey("gps"); + bool requested = setting != NULL && strcmp(setting, "1") == 0; + int prefix_len = snprintf(reply, 160, "req:%u ", requested ? 1U : 0U); + if (prefix_len < 0 || prefix_len >= 160) { + reply[159] = 0; + } else { + l->formatDiagnostics(reply + prefix_len, 160 - prefix_len); + } + } else { + strcpy(reply, "gps provider not found"); + } } else if (memcmp(command, "gps setloc", 10) == 0) { _prefs->node_lat = _sensors->node_lat; _prefs->node_lon = _sensors->node_lon; From 0a2801649610cc8d8053cc26a063832ef4c31232 Mon Sep 17 00:00:00 2001 From: F4FPR Date: Fri, 14 Aug 2026 00:20:11 +0200 Subject: [PATCH 14/45] Update MicroNMEALocationProvider.h --- src/helpers/sensors/MicroNMEALocationProvider.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index b66cb1145..a8bdbe0bf 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -51,6 +51,17 @@ class MicroNMEALocationProvider : public LocationProvider { unsigned long _last_time_sync = 0; static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes +#ifdef GPS_L76K + void configureL76K() { + // Reapply the constellation mode at each provider start instead of + // assuming it survives a receiver power cycle. + // Mode 7 enables GPS + BeiDou + GLONASS. + delay(250); + MicroNMEA::sendSentence(*_gps_serial, "$PCAS04,7"); + delay(250); + } +#endif + public : MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _gps_serial(&ser), _peripher_power(peripher_power), _pin_reset(pin_reset), _pin_en(pin_en) { @@ -91,6 +102,11 @@ public : delay(10); digitalWrite(_pin_reset, !GPS_RESET_ACTIVE); } +#ifdef GPS_L76K + // EnvironmentSensorManager calls reset() immediately after begin(). + // Configure only after that reset so the setting is not discarded. + configureL76K(); +#endif } void stop() override { From 3bcfb70141983b7b94b64108cac1cc6092b1a04c Mon Sep 17 00:00:00 2001 From: F4FPR Date: Fri, 14 Aug 2026 00:42:57 +0200 Subject: [PATCH 15/45] Update MeshCore.h --- src/MeshCore.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MeshCore.h b/src/MeshCore.h index e67371ef1..229794761 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -74,6 +74,8 @@ public: // Power management interface (boards with power management override these) virtual bool isExternalPowered() { return false; } + // True while a board can identify an active battery-charging source. + virtual bool isChargerActive() { return false; } virtual uint16_t getBootVoltage() { return 0; } virtual uint32_t getResetReason() const { return 0; } virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; } From 77057aefdd392c09c744497f6103c3003f30329f Mon Sep 17 00:00:00 2001 From: F4FPR Date: Fri, 14 Aug 2026 00:43:33 +0200 Subject: [PATCH 16/45] Update CommonCLI.cpp --- src/helpers/CommonCLI.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b318bb58e..d2a8b7076 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -273,6 +273,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); } else if (memcmp(command, "board", 5) == 0) { sprintf(reply, "%s", _board->getManufacturerName()); + } else if (strcmp(command, "power") == 0) { + snprintf(reply, 160, "batt:%u mV ext:%s charger:%s", + (unsigned)_board->getBattMilliVolts(), + _board->isExternalPowered() ? "yes" : "no", + _board->isChargerActive() ? "yes" : "no"); } else if (memcmp(command, "sensor get ", 11) == 0) { const char* key = command + 11; const char* val = _sensors->getSettingByKey(key); From 4c4497d077da23304c53890dcdee31872fb55268 Mon Sep 17 00:00:00 2001 From: F4FPR Date: Fri, 14 Aug 2026 00:44:16 +0200 Subject: [PATCH 17/45] Update ThinkNodeM6Board.cpp --- variants/thinknode_m6/ThinkNodeM6Board.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/variants/thinknode_m6/ThinkNodeM6Board.cpp b/variants/thinknode_m6/ThinkNodeM6Board.cpp index 8ebae64c6..f83f274f9 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.cpp +++ b/variants/thinknode_m6/ThinkNodeM6Board.cpp @@ -15,6 +15,9 @@ void ThinkNodeM6Board::begin() { digitalWrite(P_LORA_TX_LED, LOW); #endif + pinMode(EXT_PWR_DETECT, INPUT); + pinMode(EXT_CHRG_DETECT, INPUT); + delay(10); // give sx1262 some time to power up } From 33239520e13a384149ab920a76995175d836738d Mon Sep 17 00:00:00 2001 From: F4FPR Date: Fri, 14 Aug 2026 00:44:43 +0200 Subject: [PATCH 18/45] Update ThinkNodeM6Board.h --- variants/thinknode_m6/ThinkNodeM6Board.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h index 78815e2c9..1af952d2f 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.h +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -36,6 +36,21 @@ public: return "Elecrow ThinkNode M6"; } + bool isExternalPowered() override { + if (digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE || + digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE) { + return true; + } + return NRF52Board::isExternalPowered(); + } + + bool isChargerActive() override { + // The charge-status line covers solar input; external USB input is also a + // charging source, although the board cannot report charge completion. + return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE || + digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE; + } + void powerOff() override { // turn off all leds, sd_power_system_off will not do this for us From bb7a324471f4070966530bc3c6b7b6680164119a Mon Sep 17 00:00:00 2001 From: F4FPR Date: Fri, 14 Aug 2026 00:45:22 +0200 Subject: [PATCH 19/45] Update variant.h --- variants/thinknode_m6/variant.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/variants/thinknode_m6/variant.h b/variants/thinknode_m6/variant.h index 0a4025f3b..3428fcd64 100644 --- a/variants/thinknode_m6/variant.h +++ b/variants/thinknode_m6/variant.h @@ -29,6 +29,12 @@ #define AREF_VOLTAGE (3.0) +// External power and charger status lines. +#define EXT_PWR_DETECT (13) +#define EXT_PWR_DETECT_VALUE HIGH +#define EXT_CHRG_DETECT (15) +#define EXT_CHRG_DETECT_VALUE LOW + //////////////////////////////////////////////////////////////////////////////// // Number of pins From da863e555d811155c97bd3cda5517b42d9096bda Mon Sep 17 00:00:00 2001 From: F4FPR Date: Tue, 18 Aug 2026 19:38:23 +0200 Subject: [PATCH 20/45] Update MeshCore.h --- src/MeshCore.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/MeshCore.h b/src/MeshCore.h index 229794761..ca12daee5 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -76,6 +76,14 @@ public: virtual bool isExternalPowered() { return false; } // True while a board can identify an active battery-charging source. virtual bool isChargerActive() { return false; } + + // Optional, source-specific power detection. Boards that can distinguish a USB + // supply from a solar charger override these; defaults keep every other board + // unaffected (the generic CLI prints "n/a" when a capability is absent). + virtual bool hasUsbPowerDetect() const { return false; } + virtual bool isUsbPowered() { return false; } + virtual bool hasSolarChargerDetect() const { return false; } + virtual bool isSolarChargerActive() { return false; } virtual uint16_t getBootVoltage() { return 0; } virtual uint32_t getResetReason() const { return 0; } virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; } From 6b696a4ef6db2e757f300b1d55efa34705f4e142 Mon Sep 17 00:00:00 2001 From: F4FPR Date: Tue, 18 Aug 2026 19:39:21 +0200 Subject: [PATCH 21/45] Update CommonCLI.cpp --- src/helpers/CommonCLI.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index d2a8b7076..f2daf3cd8 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -274,8 +274,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } else if (memcmp(command, "board", 5) == 0) { sprintf(reply, "%s", _board->getManufacturerName()); } else if (strcmp(command, "power") == 0) { - snprintf(reply, 160, "batt:%u mV ext:%s charger:%s", + const char* usb_state = _board->hasUsbPowerDetect() + ? (_board->isUsbPowered() ? "yes" : "no") + : "n/a"; + const char* solar_state = _board->hasSolarChargerDetect() + ? (_board->isSolarChargerActive() ? "yes" : "no") + : "n/a"; + snprintf(reply, 160, "batt:%u mV usb:%s solar_chg:%s ext:%s charger:%s", (unsigned)_board->getBattMilliVolts(), + usb_state, + solar_state, _board->isExternalPowered() ? "yes" : "no", _board->isChargerActive() ? "yes" : "no"); } else if (memcmp(command, "sensor get ", 11) == 0) { From 2940ead5049604e75fcedf963a3b3cfb94ea9ee2 Mon Sep 17 00:00:00 2001 From: F4FPR Date: Tue, 18 Aug 2026 19:40:42 +0200 Subject: [PATCH 22/45] Update ThinkNodeM6Board.h --- variants/thinknode_m6/ThinkNodeM6Board.h | 26 ++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h index 1af952d2f..934794c9a 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.h +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -36,19 +36,33 @@ public: return "Elecrow ThinkNode M6"; } + bool hasUsbPowerDetect() const override { return true; } + + bool isUsbPowered() override { + // P0.13 is the dedicated USB / external power-detect line on the M6 PCB + // (same pin the Meshtastic M6 variant uses for USB detection). + return digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE; + } + + bool hasSolarChargerDetect() const override { return true; } + + bool isSolarChargerActive() override { + // P0.15 is the active-low solar-charger status line. It reports the + // charger's state, not photovoltaic voltage or current. + return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE; + } + bool isExternalPowered() override { - if (digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE || - digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE) { + if (isUsbPowered() || isSolarChargerActive()) { return true; } return NRF52Board::isExternalPowered(); } bool isChargerActive() override { - // The charge-status line covers solar input; external USB input is also a - // charging source, although the board cannot report charge completion. - return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE || - digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE; + // Preserve a generic charging indication while also exposing both + // source-specific states (USB / solar) separately through the CLI. + return isSolarChargerActive() || isUsbPowered(); } void powerOff() override { From 0908b034911adf827019ef58fd5c4b4a3b769320 Mon Sep 17 00:00:00 2001 From: Alex Beal Date: Tue, 25 Aug 2026 15:00:45 -0600 Subject: [PATCH 23/45] Fix: Do not misidentify IMU as an RTC on Heltec T1 The T1 has an ICM-42607-P IMU with the same address as a DS3231 RTC. This completely breaks the clock on the T1. Reading the time results in a garbage value, causing all sorts of strange issues. The time never advances, which breaks logging in to repeaters (due to their time-based replay protection). This simply disables the probe on the I2C line. The DS3231 doesn't have a WHO_AM_I register that could be used to prevent misidentifying it, so this simpler fix is used. --- variants/heltec_t1/variant.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/variants/heltec_t1/variant.h b/variants/heltec_t1/variant.h index b52aeba0a..de40ea642 100644 --- a/variants/heltec_t1/variant.h +++ b/variants/heltec_t1/variant.h @@ -9,6 +9,12 @@ #define USE_LFXO #define VARIANT_MCK (64000000ul) +// This board has no external RTC chip -- the ICM-42607-P IMU sits at 0x68, +// the same I2C address DS3231 probing checks, so without this the IMU gets +// misidentified as a DS3231 and its unrelated registers get read/written +// as if they were time/status registers, producing a bogus, unfixable clock. +#define DISABLE_DS3231_PROBE + //////////////////////////////////////////////////////////////////////////////// // Number of pins From 35142a8896733f6815b6a43bea2367d570059e7c Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Mon, 2 Mar 2026 10:11:51 +0100 Subject: [PATCH 24/45] Fix 1970 date after crash/watchdog/brownout reset on ESP32 Currently, time is only set to ~2024 when it powers on cleanly. When time is lost, this results in time dropping to 1970. Save time as backup to RTC slow memory. Should fix time staying accurate during brownouts etc. --- src/helpers/ESP32Board.h | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 75428bc69..3d1b97f45 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -200,19 +200,30 @@ public: } }; +static RTC_NOINIT_ATTR uint32_t _rtc_backup_time; +static RTC_NOINIT_ATTR uint32_t _rtc_backup_magic; +#define RTC_BACKUP_MAGIC 0xAA55CC33 +#define RTC_TIME_MIN 1772323200 // 1 Mar 2026 + class ESP32RTCClock : public mesh::RTCClock { public: ESP32RTCClock() { } void begin() { esp_reset_reason_t reason = esp_reset_reason(); - if (reason == ESP_RST_POWERON) { - // start with some date/time in the recent past - struct timeval tv; - tv.tv_sec = 1715770351; // 15 May 2024, 8:50pm + if (reason == ESP_RST_DEEPSLEEP) { + return; // ESP-IDF preserves system time across deep sleep + } + // All other resets (power-on, crash, WDT, brownout) lose system time. + // Restore from RTC backup if valid, otherwise use hardcoded seed. + struct timeval tv; + if (_rtc_backup_magic == RTC_BACKUP_MAGIC && _rtc_backup_time > RTC_TIME_MIN) { + tv.tv_sec = _rtc_backup_time; + } else { + tv.tv_sec = 1772323200; // 1 Mar 2026 + } tv.tv_usec = 0; settimeofday(&tv, NULL); } - } uint32_t getCurrentTime() override { time_t _now; time(&_now); @@ -223,6 +234,16 @@ public: tv.tv_sec = time; tv.tv_usec = 0; settimeofday(&tv, NULL); + _rtc_backup_time = time; + _rtc_backup_magic = RTC_BACKUP_MAGIC; + } + void tick() override { + time_t now; + time(&now); + if (now > RTC_TIME_MIN && (uint32_t)now != _rtc_backup_time) { + _rtc_backup_time = (uint32_t)now; + _rtc_backup_magic = RTC_BACKUP_MAGIC; + } } }; From cb6f2f22555826d1392262b1a2c24661b988c3c8 Mon Sep 17 00:00:00 2001 From: Wessel Date: Fri, 5 Jun 2026 19:00:33 +0200 Subject: [PATCH 25/45] Update src/helpers/ESP32Board.h Co-authored-by: Dan McGee --- src/helpers/ESP32Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 3d1b97f45..3f95e6923 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -219,7 +219,7 @@ public: if (_rtc_backup_magic == RTC_BACKUP_MAGIC && _rtc_backup_time > RTC_TIME_MIN) { tv.tv_sec = _rtc_backup_time; } else { - tv.tv_sec = 1772323200; // 1 Mar 2026 + tv.tv_sec = RTC_TIME_MIN; } tv.tv_usec = 0; settimeofday(&tv, NULL); From a88de69486bab69280f3ebb92e1732919c3bd61c Mon Sep 17 00:00:00 2001 From: TadyTheFish Date: Sun, 9 Aug 2026 21:27:22 +0200 Subject: [PATCH 26/45] Enable INA226 support for ProMicro nRF52840 --- variants/promicro/platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 90cb475f5..da27c2fab 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -23,6 +23,7 @@ build_flags = ${nrf52_base.build_flags} -D ENV_INCLUDE_BMP280=1 -D ENV_INCLUDE_INA3221=1 -D ENV_INCLUDE_INA219=1 + -D ENV_INCLUDE_INA226=1 build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/promicro> @@ -34,6 +35,7 @@ lib_deps= ${nrf52_base.lib_deps} adafruit/Adafruit BME280 Library @ ^2.3.0 adafruit/Adafruit BMP280 Library@^2.6.8 stevemarple/MicroNMEA @ ^2.0.6 + robtillaart/INA226@^0.6.6 [env:ProMicro_repeater] extends = Promicro From 61fcc49c8ae58cb1504be76fd9828fd4f7b30e0e Mon Sep 17 00:00:00 2001 From: Avamander Date: Mon, 14 Sep 2026 17:53:50 +0300 Subject: [PATCH 27/45] Fix Heltec Mesh Node T1 GNSS pins --- variants/heltec_t1/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_t1/variant.h b/variants/heltec_t1/variant.h index b52aeba0a..72b9a9ed5 100644 --- a/variants/heltec_t1/variant.h +++ b/variants/heltec_t1/variant.h @@ -131,8 +131,8 @@ #define GPS_EN_ACTIVE LOW #define PIN_GPS_EN_ACTIVE LOW #define PIN_GPS_PPS (32 + 9) -#define GPS_TX_PIN (0 + 8) -#define GPS_RX_PIN (0 + 7) +#define GPS_RX_PIN (0 + 8) +#define GPS_TX_PIN (0 + 7) #define PIN_GPS_TX GPS_TX_PIN #define PIN_GPS_RX GPS_RX_PIN From f9e70426ae5eed7782edd9c6b56af86180e0c63c Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 24 Sep 2026 13:35:27 +1000 Subject: [PATCH 28/45] increased MAX_SEARCH_RESULTS to 24. (was 8) Closes #3243 --- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/BaseChatMesh.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 6b7a32fef..8b421081b 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -43,7 +43,7 @@ #define FIRMWARE_ROLE "sensor" -#define MAX_SEARCH_RESULTS 8 +#define MAX_SEARCH_RESULTS 24 #define MAX_CONCURRENT_ALERTS 4 class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 0a8fdef4f..835384b74 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -9,7 +9,7 @@ #include "ContactInfo.h" -#define MAX_SEARCH_RESULTS 8 +#define MAX_SEARCH_RESULTS 24 #define MSG_SEND_FAILED 0 #define MSG_SEND_SENT_FLOOD 1 From d07803bd9bff45a2ba9b6343de81ad1a56edef78 Mon Sep 17 00:00:00 2001 From: Marcel Verdult Date: Sat, 26 Sep 2026 14:07:28 +0200 Subject: [PATCH 29/45] Fix RAK11310 build: add no-op attachDynamicPrefs() --- variants/rak11310/RAK11310Board.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/variants/rak11310/RAK11310Board.h b/variants/rak11310/RAK11310Board.h index ea0f15e26..5b75ac361 100644 --- a/variants/rak11310/RAK11310Board.h +++ b/variants/rak11310/RAK11310Board.h @@ -2,6 +2,7 @@ #include #include +#include // from https://github.com/RAKWireless/RAK11300-AT-Command-Firmware/blob/9c48409a43620a828d653501d536473200aa33af/RAK11300-AT-Arduino/batt.cpp#L17-L19 #define VBAT_MV_PER_LSB (0.806F) // 3.0V ADC range and 12 - bit ADC resolution = 3300mV / 4096 @@ -46,4 +47,6 @@ public: void reboot() override { rp2040.reboot(); } bool startOTAUpdate(const char *id, char reply[]) override; + + void attachDynamicPrefs(KeyValueStore* prefs) { } // no-op }; From 3fa8dbf4d10e780bec9ec0e5de3b1807e828830e Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 29 Sep 2026 20:10:51 +1000 Subject: [PATCH 30/45] feat: add waitFor() and drain() to LocationProvider to support sending commands to GPS --- src/helpers/sensors/LocationProvider.h | 2 ++ .../sensors/MicroNMEALocationProvider.h | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index 81d08652e..e11b738b5 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -17,6 +17,8 @@ public: virtual bool isValid() = 0; virtual long getTimestamp() = 0; virtual void sendSentence(const char * sentence); + virtual bool waitFor(const char* prefix, uint32_t timeout_ms) { return false; } + virtual void drain() { } virtual void reset() = 0; virtual void begin() = 0; virtual void stop() = 0; diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index b66cb1145..e559ad018 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -129,10 +129,31 @@ public : return dt.unixtime(); } + void drain() { while (_gps_serial->available()) _gps_serial->read(); } + + const char* getSentence() const { return nmea.getSentence(); } + void sendSentence(const char *sentence) override { nmea.sendSentence(*_gps_serial, sentence); } + bool waitFor(const char* prefix, uint32_t timeout_ms) { + size_t plen = strlen(prefix); + uint32_t timeout = millis() + timeout_ms; + while ((int32_t)(millis() - timeout) < 0) { + if (_gps_serial->available()) { + char c = _gps_serial->read(); + #ifdef GPS_NMEA_DEBUG + Serial.print(c); + #endif + if (nmea.process(c) && strncmp(nmea.getSentence(), prefix, plen) == 0) return true; + } else { + yield(); + } + } + return false; + } + void loop() override { while (_gps_serial->available()) { From 037e27833b5edc63cd905bb706c07025819114ec Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 29 Sep 2026 20:12:08 +1000 Subject: [PATCH 31/45] feat: add AirohaSleep.h helper for faster fix on AG3335 based GPS --- src/helpers/sensors/AirohaSleep.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/helpers/sensors/AirohaSleep.h diff --git a/src/helpers/sensors/AirohaSleep.h b/src/helpers/sensors/AirohaSleep.h new file mode 100644 index 000000000..6637d0fbf --- /dev/null +++ b/src/helpers/sensors/AirohaSleep.h @@ -0,0 +1,19 @@ +#pragma once +#include + +// Board must keep the GPS RTC backup pin powered and can cut GPS power once we return. +// Returns true if the command was acked within 3 attempts, otherwise returns false. +static inline bool airohaEnterSleep(LocationProvider* nmea) { + for (uint8_t attempt = 0; attempt < 3; attempt++) { + nmea->drain(); + nmea->sendSentence("$PAIR650,0"); + if (nmea->waitFor("$PAIR001,650,0", 50)) { // wait for the command received signal + #ifdef GPS_NMEA_DEBUG + Serial.printf("Airoha RTC Backup sleep command accepted by GPS after %u attempts\r\n", attempt + 1); + #endif + nmea->waitFor("$PAIR650,0", 50); // give the GPS 50ms grace to signal it is ready for sleep + return true; + } + } + return false; +} \ No newline at end of file From 11755c81996fc8cd76708d2a420aaee920a58b02 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 29 Sep 2026 20:14:19 +1000 Subject: [PATCH 32/45] MeshTracker X1 GPS: use RTC backup for faster fix --- variants/meshtracker_x1/target.cpp | 38 ++++++++---------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/variants/meshtracker_x1/target.cpp b/variants/meshtracker_x1/target.cpp index d6d548b51..5b90d6609 100644 --- a/variants/meshtracker_x1/target.cpp +++ b/variants/meshtracker_x1/target.cpp @@ -1,6 +1,7 @@ #include #include "target.h" #include +#include MeshTrackerX1Board board; @@ -27,46 +28,27 @@ mesh::LocalIdentity radio_new_identity() { void MeshTrackerX1SensorManager::start_gps() { gps_active = true; - // this init sequence comes from seeed examples and deals with all gps pins - pinMode(GPS_EN, OUTPUT); digitalWrite(GPS_EN, HIGH); delay(10); - pinMode(GPS_VRTC_EN, OUTPUT); - digitalWrite(GPS_VRTC_EN, HIGH); - delay(10); - - pinMode(GPS_RESET, OUTPUT); - digitalWrite(GPS_RESET, HIGH); - delay(10); - digitalWrite(GPS_RESET, LOW); - - pinMode(GPS_SLEEP_INT, OUTPUT); - digitalWrite(GPS_SLEEP_INT, HIGH); - pinMode(GPS_RTC_INT, OUTPUT); - digitalWrite(GPS_RTC_INT, LOW); -} - -void MeshTrackerX1SensorManager::sleep_gps() { - gps_active = false; - digitalWrite(GPS_VRTC_EN, HIGH); // keep RTC alive for faster fix on wake - digitalWrite(GPS_EN, LOW); - digitalWrite(GPS_RESET, LOW); - digitalWrite(GPS_SLEEP_INT, HIGH); + digitalWrite(GPS_RTC_INT, HIGH); + delay(5); digitalWrite(GPS_RTC_INT, LOW); } void MeshTrackerX1SensorManager::stop_gps() { gps_active = false; - digitalWrite(GPS_VRTC_EN, LOW); + digitalWrite(GPS_VRTC_EN, HIGH); // keep GPS RTC alive for faster fix on wake + digitalWrite(GPS_RTC_INT, LOW); // make sure this is LOW so we can pulse it to wake + airohaEnterSleep(_nmea); digitalWrite(GPS_EN, LOW); - digitalWrite(GPS_RESET, LOW); - digitalWrite(GPS_SLEEP_INT, HIGH); - digitalWrite(GPS_RTC_INT, LOW); } bool MeshTrackerX1SensorManager::begin() { // init GPS Serial1.begin(GPS_BAUD_RATE); + digitalWrite(GPS_RESET, HIGH); + delay(10); + digitalWrite(GPS_RESET, LOW); // init SPA06-003 barometer baro_ok = spa06.begin(SPA06_003_DEFAULT_ADDR, &Wire) || spa06.begin(0x76, &Wire); @@ -122,7 +104,7 @@ const char* MeshTrackerX1SensorManager::getSettingValue(int i) const { bool MeshTrackerX1SensorManager::setSettingValue(const char* name, const char* value) { if (strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { - sleep_gps(); // sleep for faster fix ! + stop_gps(); } else { start_gps(); } From 74413f0e4df005d3d776027439a8e1f15fd509a4 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 29 Sep 2026 20:16:32 +1000 Subject: [PATCH 33/45] T1000e GPS: use RTC backup for faster fix --- variants/t1000-e/target.cpp | 48 ++++++++---------------------------- variants/t1000-e/variant.cpp | 8 +++--- 2 files changed, 14 insertions(+), 42 deletions(-) diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 425328270..70a64359f 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -2,6 +2,7 @@ #include "t1000e_sensors.h" #include "target.h" #include +#include T1000eBoard board; @@ -81,56 +82,28 @@ mesh::LocalIdentity radio_new_identity() { void T1000SensorManager::start_gps() { gps_active = true; - //_nmea->begin(); - // this init sequence should be better - // comes from seeed examples and deals with all gps pins - pinMode(GPS_EN, OUTPUT); digitalWrite(GPS_EN, HIGH); delay(10); - pinMode(GPS_VRTC_EN, OUTPUT); - digitalWrite(GPS_VRTC_EN, HIGH); - delay(10); - - pinMode(GPS_RESET, OUTPUT); - digitalWrite(GPS_RESET, HIGH); - delay(10); - digitalWrite(GPS_RESET, LOW); - - pinMode(GPS_SLEEP_INT, OUTPUT); - digitalWrite(GPS_SLEEP_INT, HIGH); - pinMode(GPS_RTC_INT, OUTPUT); + digitalWrite(GPS_RTC_INT, HIGH); + delay(5); digitalWrite(GPS_RTC_INT, LOW); - pinMode(GPS_RESETB, INPUT_PULLUP); -} - -void T1000SensorManager::sleep_gps() { - gps_active = false; - digitalWrite(GPS_VRTC_EN, HIGH); - digitalWrite(GPS_EN, LOW); - digitalWrite(GPS_RESET, HIGH); - digitalWrite(GPS_SLEEP_INT, HIGH); - digitalWrite(GPS_RTC_INT, LOW); - pinMode(GPS_RESETB, OUTPUT); - digitalWrite(GPS_RESETB, LOW); - //_nmea->stop(); } void T1000SensorManager::stop_gps() { gps_active = false; - digitalWrite(GPS_VRTC_EN, LOW); + digitalWrite(GPS_VRTC_EN, HIGH); // keep GPS RTC alive for faster fix on wake + digitalWrite(GPS_RTC_INT, LOW); // make sure this is LOW so we can pulse it to wake + airohaEnterSleep(_nmea); // send command to put the GPS into RTC backup sleep digitalWrite(GPS_EN, LOW); - digitalWrite(GPS_RESET, HIGH); - digitalWrite(GPS_SLEEP_INT, HIGH); - digitalWrite(GPS_RTC_INT, LOW); - pinMode(GPS_RESETB, OUTPUT); - digitalWrite(GPS_RESETB, LOW); - //_nmea->stop(); } bool T1000SensorManager::begin() { // init GPS Serial1.begin(115200); + digitalWrite(GPS_RESET, HIGH); + delay(10); + digitalWrite(GPS_RESET, LOW); return true; } @@ -156,7 +129,6 @@ void T1000SensorManager::loop() { node_lat = ((double)_nmea->getLatitude())/1000000.; node_lon = ((double)_nmea->getLongitude())/1000000.; node_altitude = ((double)_nmea->getAltitude()) / 1000.0; - //Serial.printf("lat %f lon %f\r\n", _lat, _lon); } next_gps_update = millis() + 1000; } @@ -176,7 +148,7 @@ const char* T1000SensorManager::getSettingValue(int i) const { bool T1000SensorManager::setSettingValue(const char* name, const char* value) { if (strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { - sleep_gps(); // sleep for faster fix ! + stop_gps(); } else { start_gps(); } diff --git a/variants/t1000-e/variant.cpp b/variants/t1000-e/variant.cpp index ed21fd68c..fc30a6197 100644 --- a/variants/t1000-e/variant.cpp +++ b/variants/t1000-e/variant.cpp @@ -71,7 +71,7 @@ void initVariant() pinMode(LUX_SENSOR, INPUT); pinMode(EXT_CHRG_DETECT, INPUT); pinMode(EXT_PWR_DETECT, INPUT); - pinMode(GPS_RESETB, INPUT); + pinMode(GPS_RESETB, INPUT_PULLUP); pinMode(PIN_BUTTON1, INPUT); pinMode(PIN_3V3_EN, OUTPUT); @@ -89,10 +89,10 @@ void initVariant() digitalWrite(PIN_3V3_ACC_EN, LOW); digitalWrite(BUZZER_EN, LOW); digitalWrite(SENSOR_EN, LOW); - digitalWrite(GPS_EN, LOW); + digitalWrite(GPS_EN, HIGH); digitalWrite(GPS_RESET, LOW); - digitalWrite(GPS_VRTC_EN, LOW); - digitalWrite(GPS_SLEEP_INT, HIGH); + digitalWrite(GPS_VRTC_EN, HIGH); + digitalWrite(GPS_SLEEP_INT, LOW); digitalWrite(GPS_RTC_INT, LOW); digitalWrite(LED_PIN, LOW); } From a706d1d943717090030ee395ad846e62a4d6cfb3 Mon Sep 17 00:00:00 2001 From: Huw Duddy <37787853+oltaco@users.noreply.github.com> Date: Thu, 1 Oct 2026 22:17:44 +1000 Subject: [PATCH 34/45] Revert "Fix Heltec Mesh Node T1 GNSS pins" --- variants/heltec_t1/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_t1/variant.h b/variants/heltec_t1/variant.h index 70ec23f7c..de40ea642 100644 --- a/variants/heltec_t1/variant.h +++ b/variants/heltec_t1/variant.h @@ -137,8 +137,8 @@ #define GPS_EN_ACTIVE LOW #define PIN_GPS_EN_ACTIVE LOW #define PIN_GPS_PPS (32 + 9) -#define GPS_RX_PIN (0 + 8) -#define GPS_TX_PIN (0 + 7) +#define GPS_TX_PIN (0 + 8) +#define GPS_RX_PIN (0 + 7) #define PIN_GPS_TX GPS_TX_PIN #define PIN_GPS_RX GPS_RX_PIN From 35e10a9c75d71c8859369507a259b4dcb1efd813 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 2 Oct 2026 07:35:34 +1000 Subject: [PATCH 35/45] fix bad comment format --- src/helpers/nrf52/SerialBLEInterface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 2e5626a8d..fa091c031 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -129,10 +129,10 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code char charpin[20]; snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code); - #ifdef DISABLE_BLE_LED \\ use variant platformio.ini build_flags macro: -D DISABLE_BLE_LED - Bluefruit.autoConnLed(false); \\ false removes the automatic control of the LED by the Bluefruit library + #ifdef DISABLE_BLE_LED // use variant platformio.ini build_flags macro: -D DISABLE_BLE_LED + Bluefruit.autoConnLed(false); // false removes the automatic control of the LED by the Bluefruit library #else - Bluefruit.autoConnLed(true); \\ true leaves the default automatic control of the LED by the Bluefruit library + Bluefruit.autoConnLed(true); // true leaves the default automatic control of the LED by the Bluefruit library #endif // If we want to control BLE LED ourselves, uncomment this: From 0e1ba7102d776f3214e69c13239bb73bddaa618f Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 2 Oct 2026 07:40:42 +1000 Subject: [PATCH 36/45] tidy: remove old commented Bluefruit.autoConnLed(false) --- src/helpers/nrf52/SerialBLEInterface.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index fa091c031..6ed16bc86 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -135,8 +135,6 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code Bluefruit.autoConnLed(true); // true leaves the default automatic control of the LED by the Bluefruit library #endif - // If we want to control BLE LED ourselves, uncomment this: - // Bluefruit.autoConnLed(false); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); Bluefruit.begin(); From f19733e4787933e67d05e1105ce1372e3bf771f4 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 4 Oct 2026 15:02:00 +1100 Subject: [PATCH 37/45] * don't respond to DISCOVER requests if node has adverts turned off. (isHiddenNode()) --- examples/simple_repeater/MyMesh.cpp | 1 + examples/simple_repeater/MyMesh.h | 3 +++ 2 files changed, 4 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index ca6a3e607..61b6c149e 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -790,6 +790,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && !_prefs.disable_fwd && discover_limiter.allow(rtc_clock.getCurrentTime()) + && !isHiddenNode() // this node wishes to NOT be discoverable ) { int i = 1; uint8_t filter = packet->payload[i++]; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index cac6c4a28..a9ac453a0 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -187,6 +187,9 @@ public: NodePrefs* getNodePrefs() { return &_prefs; } + bool isHiddenNode() const { + return _prefs.advert_interval == 0 && _prefs.flood_advert_interval == 0; + } void savePrefs() override { _cli.savePrefs(_fs); From 4c2af58d3e5e25eed9534ff012cd125134234b79 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Tue, 6 Oct 2026 18:50:15 +0200 Subject: [PATCH 38/45] Use consistent `_OTA` suffix for nRF52 OTA BLE names --- variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h | 2 +- variants/meshtiny/MeshtinyBoard.h | 2 +- variants/wio-tracker-l1-1w/WioTrackerL1Board.h | 2 +- variants/wio-tracker-l1/WioTrackerL1Board.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h b/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h index 30bbc3a5c..f7ef536ba 100644 --- a/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h +++ b/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h @@ -9,7 +9,7 @@ protected: uint8_t btn_prev_state; public: - TImpulsePlusBoard() : NRF52Board("T-Impulse-Plus OTA") {} + TImpulsePlusBoard() : NRF52Board("T-Impulse-Plus_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/meshtiny/MeshtinyBoard.h b/variants/meshtiny/MeshtinyBoard.h index 67a521fe9..21673d0aa 100644 --- a/variants/meshtiny/MeshtinyBoard.h +++ b/variants/meshtiny/MeshtinyBoard.h @@ -9,7 +9,7 @@ protected: uint8_t btn_prev_state; public: - MeshtinyBoard() : NRF52Board("Meshtiny OTA") {} + MeshtinyBoard() : NRF52Board("Meshtiny_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/wio-tracker-l1-1w/WioTrackerL1Board.h b/variants/wio-tracker-l1-1w/WioTrackerL1Board.h index 798145db0..f6164e47a 100644 --- a/variants/wio-tracker-l1-1w/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1-1w/WioTrackerL1Board.h @@ -14,7 +14,7 @@ protected: bool isLoRaFemPaGainEnabled() const { return _is_pa_enabled; } public: - WioTrackerL1Board() : NRF52Board("WioTrackerL1 OTA") {} + WioTrackerL1Board() : NRF52Board("WioTrackerL1_OTA") {} void begin(); void attachDynamicPrefs(KeyValueStore* prefs); bool handleCommand(const char* command, uint32_t sender_timestamp, char* reply) override; diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index e376e0666..99e852bb7 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -9,7 +9,7 @@ protected: uint8_t btn_prev_state; public: - WioTrackerL1Board() : NRF52Board("WioTrackerL1 OTA") {} + WioTrackerL1Board() : NRF52Board("WioTrackerL1_OTA") {} void begin(); #if defined(P_LORA_TX_LED) From 47c3005b08624f55d1ef6848d9af5e458d702143 Mon Sep 17 00:00:00 2001 From: quyenlt298 <61147413+quyenlt298@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:09:36 +0700 Subject: [PATCH 39/45] Add Nelos allocation for GroupData --- docs/number_allocations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/number_allocations.md b/docs/number_allocations.md index b8cc22a21..79cc90b84 100644 --- a/docs/number_allocations.md +++ b/docs/number_allocations.md @@ -19,6 +19,7 @@ Once you have a working app/project, you need to be able to demonstrate it exist | 0110 - 011F | Ripple | ripple_biz@protonmail.com — https://buymeacoffee.com/ripplebiz | | 0120 | MCO Advanced | most.original.address@gmail.com — https://hdden.ru/MCOa/ | | 0130 - 013F | StreamSensor | william@housedillon.com - https://housedillon.com/blog/lora-e5-with-seeed-fusion | +| 0140 | Nelos | support@nelos.app — https://nelos.app | | FF00 - FFFF | -reserved for testing/dev- | | (add rows, inside the range 0100 - FEFF for custom apps) From 01f9293be073937791cdb6594c08d7bcc7f7f3ed Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 8 Oct 2026 19:56:45 +1100 Subject: [PATCH 40/45] * treat transport code 0 the same as un-scoped. --- src/helpers/RegionMap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 4667e0038..d8d773803 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -188,6 +188,7 @@ int RegionMap::getTransportKeysFor(const RegionEntry& src, TransportKey dest[], } RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { + if (packet->transport_codes[0] == 0) return &wildcard; for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param) From 6df5aaed4335d41e092f9f9c2516903dc9f451ff Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 12 Oct 2026 10:44:42 +1100 Subject: [PATCH 41/45] * ACK forwarding (in direct mode) had zero delay --- src/Mesh.cpp | 12 ++++++------ src/Mesh.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index c11f37cac..9dc42f0b0 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -87,13 +87,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } if (self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) && allowPacketForward(pkt)) { + uint32_t d = getDirectRetransmitDelay(pkt); if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { - return forwardMultipartDirect(pkt); + return forwardMultipartDirect(pkt, d); } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { if (!_tables->wasSeen(pkt)) { // don't retransmit! _tables->markSeen(pkt); removeSelfFromPath(pkt); - routeDirectRecvAcks(pkt, 0); + routeDirectRecvAcks(pkt, d); } return ACTION_RELEASE; } @@ -102,7 +103,6 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { _tables->markSeen(pkt); removeSelfFromPath(pkt); - uint32_t d = getDirectRetransmitDelay(pkt); return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority } } @@ -356,7 +356,7 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) { return ACTION_RELEASE; } -DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) { +DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt, uint32_t delay_millis) { uint8_t remaining = pkt->payload[0] >> 4; // num of packets in this multipart sequence still to be sent uint8_t type = pkt->payload[0] & 0x0F; @@ -370,7 +370,7 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) { if (!_tables->wasSeen(&tmp)) { // don't retransmit! _tables->markSeen(&tmp); removeSelfFromPath(&tmp); - routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2) + routeDirectRecvAcks(&tmp, delay_millis + ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2) } } return ACTION_RELEASE; @@ -380,13 +380,13 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) { if (!packet->isMarkedDoNotRetransmit()) { uint8_t extra = getExtraAckTransmitCount(); while (extra > 0) { - delay_millis += getDirectRetransmitDelay(packet) + 300; auto a1 = createMultiAck(packet->payload, packet->payload_len, extra); if (a1) { a1->path_len = Packet::copyPath(a1->path, packet->path, packet->path_len); a1->header &= ~PH_ROUTE_MASK; a1->header |= ROUTE_TYPE_DIRECT; sendPacket(a1, 0, delay_millis); + delay_millis += 300; } extra--; } diff --git a/src/Mesh.h b/src/Mesh.h index 49a299a6a..22bb5c6d5 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -32,7 +32,7 @@ class Mesh : public Dispatcher { void removeSelfFromPath(Packet* packet); void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis); //void routeRecvAcks(Packet* packet, uint32_t delay_millis); - DispatcherAction forwardMultipartDirect(Packet* pkt); + DispatcherAction forwardMultipartDirect(Packet* pkt, uint32_t delay_millis); protected: DispatcherAction onRecvPacket(Packet* pkt) override; From 3d1a5d4c64eec2e3310593fe7112f2ca02a05772 Mon Sep 17 00:00:00 2001 From: agessaman Date: Sun, 11 Oct 2026 20:51:18 -0700 Subject: [PATCH 42/45] feat(variants): add Waveshare ESP32-S3-LR1121 ESP32-S3FH4R2 (4MB flash, 2MB PSRAM, native USB-CDC) with an LR1121. Pins, the DIO5/DIO6 RF switch table and the 1.8 V TCXO follow the HF schematic and Waveshare's examples. Envs: repeater, room server, companion USB/BLE and KISS modem, all on min_spiffs so roles share one partition layout. Only RFO_HP_LF is wired (R4 on RFO_LP_LF is not fitted), so the new opt-in LR1121_FORCE_HP_PA flag keeps RadioLib on the HP PA at <= 14 dBm, where it would otherwise select the unconnected LP PA. --- src/helpers/radiolib/CustomLR1121.h | 7 ++ .../WaveshareS3LR1121Board.h | 11 ++ .../waveshare_esp32s3_lr1121/platformio.ini | 102 ++++++++++++++++++ variants/waveshare_esp32s3_lr1121/target.cpp | 63 +++++++++++ variants/waveshare_esp32s3_lr1121/target.h | 17 +++ 5 files changed, 200 insertions(+) create mode 100644 variants/waveshare_esp32s3_lr1121/WaveshareS3LR1121Board.h create mode 100644 variants/waveshare_esp32s3_lr1121/platformio.ini create mode 100644 variants/waveshare_esp32s3_lr1121/target.cpp create mode 100644 variants/waveshare_esp32s3_lr1121/target.h diff --git a/src/helpers/radiolib/CustomLR1121.h b/src/helpers/radiolib/CustomLR1121.h index 67e3b52a9..e8c6c431d 100644 --- a/src/helpers/radiolib/CustomLR1121.h +++ b/src/helpers/radiolib/CustomLR1121.h @@ -36,6 +36,13 @@ class CustomLR1121 : public LR1121 { return len; } +#ifdef LR1121_FORCE_HP_PA + // boards with only RFO_HP_LF wired: stop RadioLib switching to the LP PA at <= 14 dBm (2.4 GHz still uses the HF PA) + int16_t setOutputPower(int8_t power) override { + return LR1121::setOutputPower(power, true); + } +#endif + float getFreqMHz() const { return freqMHz; } int16_t setRxBoostedGainMode(bool en) { diff --git a/variants/waveshare_esp32s3_lr1121/WaveshareS3LR1121Board.h b/variants/waveshare_esp32s3_lr1121/WaveshareS3LR1121Board.h new file mode 100644 index 000000000..8307ba505 --- /dev/null +++ b/variants/waveshare_esp32s3_lr1121/WaveshareS3LR1121Board.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class WaveshareS3LR1121Board : public ESP32Board { +public: + const char* getManufacturerName() const override { + return "Waveshare ESP32-S3-LR1121"; + } +}; diff --git a/variants/waveshare_esp32s3_lr1121/platformio.ini b/variants/waveshare_esp32s3_lr1121/platformio.ini new file mode 100644 index 000000000..8e7b52d4f --- /dev/null +++ b/variants/waveshare_esp32s3_lr1121/platformio.ini @@ -0,0 +1,102 @@ +; Waveshare ESP32-S3-LR1121 (HF: 850-930 MHz + 2.4 GHz) +; ESP32-S3FH4R2 (4MB flash, 2MB PSRAM), native USB-CDC via the Tiny-Adapter FPC. +; No user LED, battery sense or user button (BOOT is GPIO0). +[Waveshare_S3_LR1121] +extends = esp32_base +board = esp32-s3-zero +; min_spiffs: 1.9MB OTA app slots; one layout for every role so switching roles keeps the filesystem +board_build.partitions = min_spiffs.csv +build_flags = ${esp32_base.build_flags} + -I variants/waveshare_esp32s3_lr1121 + -D BOARD_HAS_PSRAM + -D P_LORA_DIO_1=38 + -D P_LORA_NSS=42 + -D P_LORA_RESET=39 + -D P_LORA_BUSY=41 + -D P_LORA_SCLK=40 + -D P_LORA_MISO=46 + -D P_LORA_MOSI=45 + -D RADIO_CLASS=CustomLR1121 + -D WRAPPER_CLASS=CustomLR1121Wrapper + -D LR11X0_DIO3_TCXO_VOLTAGE=1.8 + -D LR1121_FORCE_HP_PA ; RFO_LP_LF is not fitted (R4 NC) + -D RX_BOOSTED_GAIN=true + -D LORA_TX_POWER=22 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/waveshare_esp32s3_lr1121> +lib_deps = + ${esp32_base.lib_deps} + +[env:Waveshare_S3_LR1121_repeater] +extends = Waveshare_S3_LR1121 +build_flags = + ${Waveshare_S3_LR1121.build_flags} + -D ADVERT_NAME='"Waveshare LR1121 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Waveshare_S3_LR1121.build_src_filter} + +<../examples/simple_repeater/*.cpp> +lib_deps = + ${Waveshare_S3_LR1121.lib_deps} + ${esp32_ota.lib_deps} + +[env:Waveshare_S3_LR1121_room_server] +extends = Waveshare_S3_LR1121 +build_flags = + ${Waveshare_S3_LR1121.build_flags} + -D ADVERT_NAME='"Waveshare LR1121 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Waveshare_S3_LR1121.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${Waveshare_S3_LR1121.lib_deps} + ${esp32_ota.lib_deps} + +[env:Waveshare_S3_LR1121_companion_radio_usb] +extends = Waveshare_S3_LR1121 +build_flags = + ${Waveshare_S3_LR1121.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Waveshare_S3_LR1121.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Waveshare_S3_LR1121.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Waveshare_S3_LR1121_companion_radio_ble] +extends = Waveshare_S3_LR1121 +build_flags = + ${Waveshare_S3_LR1121.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Waveshare_S3_LR1121.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Waveshare_S3_LR1121.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Waveshare_S3_LR1121_kiss_modem] +extends = Waveshare_S3_LR1121 +build_src_filter = ${Waveshare_S3_LR1121.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/waveshare_esp32s3_lr1121/target.cpp b/variants/waveshare_esp32s3_lr1121/target.cpp new file mode 100644 index 000000000..56775c4ce --- /dev/null +++ b/variants/waveshare_esp32s3_lr1121/target.cpp @@ -0,0 +1,63 @@ +#include +#include "target.h" + +WaveshareS3LR1121Board board; + +static SPIClass spi; +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +// DIO5/DIO6 drive the RTC6603SP sub-GHz T/R switch; the 2.4 GHz port (RFIO_HF) is unswitched +static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC +}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + { LR11x0::MODE_STBY, {LOW, LOW}}, + { LR11x0::MODE_RX, {HIGH, LOW}}, + { LR11x0::MODE_TX, {LOW, HIGH}}, + { LR11x0::MODE_TX_HP, {LOW, HIGH}}, + { LR11x0::MODE_TX_HF, {LOW, LOW}}, + { LR11x0::MODE_GNSS, {LOW, LOW}}, + { LR11x0::MODE_WIFI, {LOW, LOW}}, + END_OF_MODE_TABLE, +}; + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, + LORA_TX_POWER, 16, LR11X0_DIO3_TCXO_VOLTAGE); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; + } + + radio.setCRC(2); + radio.explicitHeader(); + radio.setRfSwitchTable(rfswitch_dios, rfswitch_table); +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + + return true; +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} diff --git a/variants/waveshare_esp32s3_lr1121/target.h b/variants/waveshare_esp32s3_lr1121/target.h new file mode 100644 index 000000000..461509e51 --- /dev/null +++ b/variants/waveshare_esp32s3_lr1121/target.h @@ -0,0 +1,17 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include "WaveshareS3LR1121Board.h" + +extern WaveshareS3LR1121Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +bool radio_init(); +mesh::LocalIdentity radio_new_identity(); From eac6e66b1bf423e68bdc26cbf1180096c33340ef Mon Sep 17 00:00:00 2001 From: agessaman Date: Sun, 11 Oct 2026 20:53:23 -0700 Subject: [PATCH 43/45] fix(radiolib): keep LR1121 tx power valid across sub-GHz and 2.4 GHz - Clip requested power to the active PA's range instead of failing. A stored 22 dBm was rejected at 2.4 GHz (HF PA max 13 dBm), and since setTxPower ignores errors the sub-GHz PA stayed configured. - Re-apply the requested power after every successful retune: PA choice depends on the band, and set radio / tempradio only call setParams. --- src/helpers/radiolib/CustomLR1121.h | 30 +++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1121.h b/src/helpers/radiolib/CustomLR1121.h index e8c6c431d..7182d1d5c 100644 --- a/src/helpers/radiolib/CustomLR1121.h +++ b/src/helpers/radiolib/CustomLR1121.h @@ -9,6 +9,8 @@ class CustomLR1121 : public LR1121 { uint32_t _activityAt = 0; bool _headerSeen = false; bool _rx_boosted = false; + int8_t _txPower = 0; + bool _txPowerSet = false; public: CustomLR1121(Module *mod) : LR1121(mod) { } @@ -36,12 +38,32 @@ class CustomLR1121 : public LR1121 { return len; } -#ifdef LR1121_FORCE_HP_PA - // boards with only RFO_HP_LF wired: stop RadioLib switching to the LP PA at <= 14 dBm (2.4 GHz still uses the HF PA) + using LR1121::setOutputPower; + using LR1121::setFrequency; + int16_t setOutputPower(int8_t power) override { - return LR1121::setOutputPower(power, true); - } +#ifdef LR1121_FORCE_HP_PA + const bool force_hp = true; // boards with only RFO_HP_LF wired: never select the LP PA +#else + const bool force_hp = false; #endif + // clip to the active PA's range (HF PA tops out at 13 dBm) rather than fail and keep the other band's PA + int8_t clipped = power; + checkOutputPower(power, &clipped, force_hp); + int16_t state = LR1121::setOutputPower(clipped, force_hp); + if (state == RADIOLIB_ERR_NONE) { + _txPower = power; + _txPowerSet = true; + } + return state; + } + + int16_t setFrequency(float freq) override { + int16_t state = LR1121::setFrequency(freq); + // PA selection depends on the band, and set radio / tempradio don't call setTxPower + if (state == RADIOLIB_ERR_NONE && _txPowerSet) state = setOutputPower(_txPower); + return state; + } float getFreqMHz() const { return freqMHz; } From 8579c667472d95d7873ea91a8ff5b219ff48cb85 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 14 Oct 2026 11:48:58 +1100 Subject: [PATCH 44/45] * Room: "set repeat" CLI now deprecated --- examples/simple_room_server/MyMesh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 71b8d32a3..458537e62 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -983,6 +983,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply addSystemPost(msg); snprintf(reply, MAX_POST_TEXT_LEN, "OK"); } + } else if (memcmp(command, "set repeat ", 11) == 0) { + strcpy(reply, "ERR - repeat now deprecated"); } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } From 481f72cae7bf9bee43c3e47cfb21138a1cb34cc0 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 14 Oct 2026 12:21:38 +1100 Subject: [PATCH 45/45] * only 'repeat on' now intercepted --- examples/simple_room_server/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 458537e62..da4c776c0 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -983,7 +983,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply addSystemPost(msg); snprintf(reply, MAX_POST_TEXT_LEN, "OK"); } - } else if (memcmp(command, "set repeat ", 11) == 0) { + } else if (memcmp(command, "set repeat on", 13) == 0) { strcpy(reply, "ERR - repeat now deprecated"); } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands