From c89a0e9929f4247d40569f568ccb1d216cc66762 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 22 Jan 2026 15:03:40 +0800 Subject: [PATCH 01/57] Add an external watchdog module --- examples/companion_radio/main.cpp | 7 ++++++ examples/simple_repeater/main.cpp | 12 +++++++++- examples/simple_room_server/main.cpp | 7 ++++++ examples/simple_secure_chat/main.cpp | 7 ++++++ examples/simple_sensor/main.cpp | 7 ++++++ src/helpers/ExWatchdogManager.h | 11 +++++++++ variants/heltec_mesh_solar/platformio.ini | 5 ++++ variants/heltec_mesh_solar/target.cpp | 28 +++++++++++++++++++++++ variants/heltec_mesh_solar/target.h | 11 +++++++++ variants/heltec_mesh_solar/variant.h | 4 ++-- 10 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/helpers/ExWatchdogManager.h diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d..2035d789 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -110,6 +110,10 @@ void setup() { board.begin(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.begin(); +#endif + #ifdef DISPLAY_CLASS DisplayDriver* disp = NULL; if (display.begin()) { @@ -228,4 +232,7 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.loop(); +#endif } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 8c745613..f371cb46 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -29,6 +29,10 @@ void setup() { board.begin(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.begin(); +#endif + // For power saving lastActive = millis(); // mark last active time since boot @@ -124,11 +128,17 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); - +#ifdef HAS_EX_WATCHDOG + ex_watchdog.loop(); +#endif if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep if (!the_mesh.hasPendingWork()) { // No pending work. Safe to sleep +#ifdef HAS_EX_WATCHDOG + board.sleep(ex_watchdog.getIntervalMs()>1800?1800:ex_watchdog.getIntervalMs()); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet +#else board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet +#endif lastActive = millis(); nextSleepinSecs = 5; // Default: To work for 5s and sleep again } else { diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 1a3b4d6e..e468b84e 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -24,6 +24,10 @@ void setup() { board.begin(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.begin(); +#endif + #ifdef DISPLAY_CLASS if (display.begin()) { display.startFrame(); @@ -111,4 +115,7 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.loop(); +#endif } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index da1bac5b..693ebfee 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -559,6 +559,10 @@ void setup() { board.begin(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.begin(); +#endif + if (!radio_init()) { halt(); } fast_rng.begin(radio_get_rng_seed()); @@ -588,4 +592,7 @@ void setup() { void loop() { the_mesh.loop(); rtc_clock.tick(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.loop(); +#endif } diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index a5fcc148..9712a207 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -58,6 +58,10 @@ void setup() { board.begin(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.begin(); +#endif + #ifdef DISPLAY_CLASS if (display.begin()) { display.startFrame(); @@ -145,4 +149,7 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.loop(); +#endif } diff --git a/src/helpers/ExWatchdogManager.h b/src/helpers/ExWatchdogManager.h new file mode 100644 index 00000000..65f178b0 --- /dev/null +++ b/src/helpers/ExWatchdogManager.h @@ -0,0 +1,11 @@ +#pragma once + +class ExWatchdogManager { +public: + unsigned long next_feed_watchdog; + ExWatchdogManager() { next_feed_watchdog = 0; } + virtual bool begin() { return false; } + virtual void loop() { } + virtual unsigned long getIntervalMs() const { return 0; } + virtual void feed() { } +}; diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index 7bfbac85..3eb51f0a 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -14,6 +14,11 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D HAS_EX_WATCHDOG + -D EX_WATCHDOG_DONE_PIN=9 + -D EX_WATCHDOG_WAKE_PIN=10 + -D EX_WATCHDOG_TIMEOUT_MS=480000 ;(6*60*1000) ; 6 minute watchdog + build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/heltec_mesh_solar> diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index ad79f717..060f20c3 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -13,6 +13,7 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); SolarSensorManager sensors = SolarSensorManager(nmea); +SolarExWatchdog ex_watchdog; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -121,3 +122,30 @@ bool SolarSensorManager::setSettingValue(const char* name, const char* value) { } return false; // not supported } + +bool SolarExWatchdog::begin() { + next_feed_watchdog = 0; + pinMode(EX_WATCHDOG_WAKE_PIN, INPUT); + pinMode(EX_WATCHDOG_DONE_PIN, OUTPUT); + delay(1); + digitalWrite(EX_WATCHDOG_DONE_PIN, LOW); + delay(1); + feed(); + return true; +} +void SolarExWatchdog::loop() { + if (millis() > next_feed_watchdog) { + feed(); + next_feed_watchdog = millis() + EX_WATCHDOG_TIMEOUT_MS; + } +} + +unsigned long SolarExWatchdog::getIntervalMs() const { + return next_feed_watchdog - millis(); +} + +void SolarExWatchdog::feed() { + digitalWrite(EX_WATCHDOG_DONE_PIN, HIGH); + delay(1); + digitalWrite(EX_WATCHDOG_DONE_PIN, LOW); +} \ No newline at end of file diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h index e301a273..c535f68e 100644 --- a/variants/heltec_mesh_solar/target.h +++ b/variants/heltec_mesh_solar/target.h @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef DISPLAY_CLASS #include #endif @@ -30,10 +31,20 @@ public: bool setSettingValue(const char* name, const char* value) override; }; +class SolarExWatchdog : public ExWatchdogManager { +public: + SolarExWatchdog() {} + bool begin() override; + void loop() override; + unsigned long getIntervalMs() const override; + void feed() override; +}; + extern MeshSolarBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SolarSensorManager sensors; +extern SolarExWatchdog ex_watchdog; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; diff --git a/variants/heltec_mesh_solar/variant.h b/variants/heltec_mesh_solar/variant.h index 14956619..3c1b378d 100644 --- a/variants/heltec_mesh_solar/variant.h +++ b/variants/heltec_mesh_solar/variant.h @@ -34,8 +34,8 @@ #define PIN_SERIAL1_RX (37) #define PIN_SERIAL1_TX (39) -#define PIN_SERIAL2_RX (9) -#define PIN_SERIAL2_TX (10) +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) //////////////////////////////////////////////////////////////////////////////// // I2C pin definition From 53ff4ed57f69c38c5aae7e41e51aa1016cdf2016 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Mon, 9 Feb 2026 11:43:41 +0800 Subject: [PATCH 02/57] Fix watchdog code in repeater --- examples/simple_repeater/main.cpp | 23 ++++++++++++++++++++--- src/helpers/ExWatchdogManager.h | 3 ++- variants/heltec_mesh_solar/target.cpp | 9 +++++++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 6cc2ac77..1760abea 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -29,6 +29,10 @@ void setup() { board.begin(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.begin(); +#endif + #if defined(MESH_DEBUG) && defined(NRF52_PLATFORM) // give some extra time for serial to settle so // boot debug messages can be seen on terminal @@ -134,11 +138,24 @@ void loop() { #endif rtc_clock.tick(); +#ifdef HAS_EX_WATCHDOG + ex_watchdog.loop(); +#endif if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { - #if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) +#ifdef HAS_EX_WATCHDOG + uint32_t sleep_interval = ex_watchdog.getIntervalMs()/1000; + board.sleep((sleep_interval > 1800) ? 1800 : sleep_interval); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet +#else + board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet +#endif board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible - #else +#else if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep +#ifdef HAS_EX_WATCHDOG + uint32_t sleep_interval = ex_watchdog.getIntervalMs()/1000; + board.sleep((sleep_interval > 1800) ? 1800 : sleep_interval); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet +#else board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet #endif lastActive = millis(); @@ -146,6 +163,6 @@ void loop() { } else { nextSleepinSecs += 5; // When there is pending work, to work another 5s } - #endif +#endif } } diff --git a/src/helpers/ExWatchdogManager.h b/src/helpers/ExWatchdogManager.h index 65f178b0..4df901b7 100644 --- a/src/helpers/ExWatchdogManager.h +++ b/src/helpers/ExWatchdogManager.h @@ -1,8 +1,9 @@ #pragma once class ExWatchdogManager { -public: +protected: unsigned long next_feed_watchdog; +public: ExWatchdogManager() { next_feed_watchdog = 0; } virtual bool begin() { return false; } virtual void loop() { } diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index c9172aa1..7eb44bfa 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -141,11 +141,16 @@ void SolarExWatchdog::loop() { } unsigned long SolarExWatchdog::getIntervalMs() const { - return next_feed_watchdog - millis(); + unsigned long interval_ms = 0; + interval_ms = next_feed_watchdog - millis(); + if(interval_ms > EX_WATCHDOG_TIMEOUT_MS) { + interval_ms = EX_WATCHDOG_TIMEOUT_MS; + } + return interval_ms; } void SolarExWatchdog::feed() { digitalWrite(EX_WATCHDOG_DONE_PIN, HIGH); delay(1); digitalWrite(EX_WATCHDOG_DONE_PIN, LOW); -} \ No newline at end of file +} From 1ab8a6f6da2de8c94c94d1e29b93bdaeef02ef6a Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 4 Mar 2026 03:13:53 +0100 Subject: [PATCH 03/57] Address review comments --- examples/companion_radio/main.cpp | 8 +++--- examples/simple_repeater/main.cpp | 16 ++++++------ examples/simple_room_server/main.cpp | 8 +++--- examples/simple_secure_chat/main.cpp | 8 +++--- examples/simple_sensor/main.cpp | 8 +++--- ...dogManager.h => ExternalWatchdogManager.h} | 4 +-- variants/heltec_mesh_solar/platformio.ini | 8 +++--- variants/heltec_mesh_solar/target.cpp | 26 +++++++++---------- variants/heltec_mesh_solar/target.h | 8 +++--- 9 files changed, 47 insertions(+), 47 deletions(-) rename src/helpers/{ExWatchdogManager.h => ExternalWatchdogManager.h} (71%) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 3c69625a..88ef3924 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -110,8 +110,8 @@ void setup() { board.begin(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.begin(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.begin(); #endif #ifdef DISPLAY_CLASS @@ -229,7 +229,7 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.loop(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.loop(); #endif } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 1760abea..f0271c88 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -29,8 +29,8 @@ void setup() { board.begin(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.begin(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.begin(); #endif #if defined(MESH_DEBUG) && defined(NRF52_PLATFORM) @@ -138,13 +138,13 @@ void loop() { #endif rtc_clock.tick(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.loop(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.loop(); #endif if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { #if defined(NRF52_PLATFORM) -#ifdef HAS_EX_WATCHDOG - uint32_t sleep_interval = ex_watchdog.getIntervalMs()/1000; +#ifdef HAS_EXTERNAL_WATCHDOG + uint32_t sleep_interval = external_watchdog.getIntervalMs()/1000; board.sleep((sleep_interval > 1800) ? 1800 : sleep_interval); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet #else board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet @@ -152,8 +152,8 @@ void loop() { board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible #else if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep -#ifdef HAS_EX_WATCHDOG - uint32_t sleep_interval = ex_watchdog.getIntervalMs()/1000; +#ifdef HAS_EXTERNAL_WATCHDOG + uint32_t sleep_interval = external_watchdog.getIntervalMs()/1000; board.sleep((sleep_interval > 1800) ? 1800 : sleep_interval); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet #else board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index bb6a860b..d20ebc6d 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -24,8 +24,8 @@ void setup() { board.begin(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.begin(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.begin(); #endif #ifdef DISPLAY_CLASS @@ -117,7 +117,7 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.loop(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.loop(); #endif } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index a2182236..a21741db 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -559,8 +559,8 @@ void setup() { board.begin(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.begin(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.begin(); #endif if (!radio_init()) { halt(); } @@ -594,7 +594,7 @@ void setup() { void loop() { the_mesh.loop(); rtc_clock.tick(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.loop(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.loop(); #endif } diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index e9ff9387..346f4dfc 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -58,8 +58,8 @@ void setup() { board.begin(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.begin(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.begin(); #endif #ifdef DISPLAY_CLASS @@ -151,7 +151,7 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); -#ifdef HAS_EX_WATCHDOG - ex_watchdog.loop(); +#ifdef HAS_EXTERNAL_WATCHDOG + external_watchdog.loop(); #endif } diff --git a/src/helpers/ExWatchdogManager.h b/src/helpers/ExternalWatchdogManager.h similarity index 71% rename from src/helpers/ExWatchdogManager.h rename to src/helpers/ExternalWatchdogManager.h index 4df901b7..9ef8abaa 100644 --- a/src/helpers/ExWatchdogManager.h +++ b/src/helpers/ExternalWatchdogManager.h @@ -1,10 +1,10 @@ #pragma once -class ExWatchdogManager { +class ExternalWatchdogManager { protected: unsigned long next_feed_watchdog; public: - ExWatchdogManager() { next_feed_watchdog = 0; } + ExternalWatchdogManager() { next_feed_watchdog = 0; } virtual bool begin() { return false; } virtual void loop() { } virtual unsigned long getIntervalMs() const { return 0; } diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index 3eb51f0a..1bc7e7fa 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -14,10 +14,10 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D HAS_EX_WATCHDOG - -D EX_WATCHDOG_DONE_PIN=9 - -D EX_WATCHDOG_WAKE_PIN=10 - -D EX_WATCHDOG_TIMEOUT_MS=480000 ;(6*60*1000) ; 6 minute watchdog + -D HAS_EXTERNAL_WATCHDOG + -D EXTERNAL_WATCHDOG_DONE_PIN=9 + -D EXTERNAL_WATCHDOG_WAKE_PIN=10 + -D EXTERNAL_WATCHDOG_TIMEOUT_MS=480000 ;(6*60*1000) ; 6 minute watchdog build_src_filter = ${nrf52_base.build_src_filter} + diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index 7eb44bfa..fa234202 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -13,7 +13,7 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); SolarSensorManager sensors = SolarSensorManager(nmea); -SolarExWatchdog ex_watchdog; +SolarExternalWatchdog external_watchdog; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -123,34 +123,34 @@ bool SolarSensorManager::setSettingValue(const char* name, const char* value) { return false; // not supported } -bool SolarExWatchdog::begin() { +bool SolarExternalWatchdog::begin() { next_feed_watchdog = 0; - pinMode(EX_WATCHDOG_WAKE_PIN, INPUT); - pinMode(EX_WATCHDOG_DONE_PIN, OUTPUT); + pinMode(EXTERNAL_WATCHDOG_WAKE_PIN, INPUT); + pinMode(EXTERNAL_WATCHDOG_DONE_PIN, OUTPUT); delay(1); - digitalWrite(EX_WATCHDOG_DONE_PIN, LOW); + digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, LOW); delay(1); feed(); return true; } -void SolarExWatchdog::loop() { +void SolarExternalWatchdog::loop() { if (millis() > next_feed_watchdog) { feed(); - next_feed_watchdog = millis() + EX_WATCHDOG_TIMEOUT_MS; + next_feed_watchdog = millis() + EXTERNAL_WATCHDOG_TIMEOUT_MS; } } -unsigned long SolarExWatchdog::getIntervalMs() const { +unsigned long SolarExternalWatchdog::getIntervalMs() const { unsigned long interval_ms = 0; interval_ms = next_feed_watchdog - millis(); - if(interval_ms > EX_WATCHDOG_TIMEOUT_MS) { - interval_ms = EX_WATCHDOG_TIMEOUT_MS; + if(interval_ms > EXTERNAL_WATCHDOG_TIMEOUT_MS) { + interval_ms = EXTERNAL_WATCHDOG_TIMEOUT_MS; } return interval_ms; } -void SolarExWatchdog::feed() { - digitalWrite(EX_WATCHDOG_DONE_PIN, HIGH); +void SolarExternalWatchdog::feed() { + digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, HIGH); delay(1); - digitalWrite(EX_WATCHDOG_DONE_PIN, LOW); + digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, LOW); } diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h index 3fc78672..124e3afb 100644 --- a/variants/heltec_mesh_solar/target.h +++ b/variants/heltec_mesh_solar/target.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include #ifdef DISPLAY_CLASS #include #endif @@ -31,9 +31,9 @@ public: bool setSettingValue(const char* name, const char* value) override; }; -class SolarExWatchdog : public ExWatchdogManager { +class SolarExternalWatchdog : public ExternalWatchdogManager { public: - SolarExWatchdog() {} + SolarExternalWatchdog() {} bool begin() override; void loop() override; unsigned long getIntervalMs() const override; @@ -44,7 +44,7 @@ extern MeshSolarBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SolarSensorManager sensors; -extern SolarExWatchdog ex_watchdog; +extern SolarExternalWatchdog external_watchdog; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 363ac44461a9299c9a43641598c18ba65fb6ffea Mon Sep 17 00:00:00 2001 From: Quency-D Date: Mon, 27 Apr 2026 15:32:02 +0800 Subject: [PATCH 04/57] Fix external watchdog timing around sleep --- examples/simple_repeater/main.cpp | 11 ++++++----- src/helpers/ExternalWatchdogManager.h | 4 ++-- variants/heltec_mesh_solar/platformio.ini | 4 ++-- variants/heltec_mesh_solar/target.cpp | 15 +++++++-------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index f636c32a..c4e318d5 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -164,16 +164,17 @@ void loop() { if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { #if defined(NRF52_PLATFORM) #ifdef HAS_EXTERNAL_WATCHDOG - uint32_t sleep_interval = external_watchdog.getIntervalMs()/1000; - board.sleep((sleep_interval > 1800) ? 1800 : sleep_interval); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet + external_watchdog.feed(); + uint32_t sleep_interval = external_watchdog.getIntervalMs() / 1000; + board.sleep((sleep_interval > 1800) ? 1800 : sleep_interval); // nrf ignores seconds param, sleeps whenever possible #else - board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet -#endif board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible +#endif #else if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep #ifdef HAS_EXTERNAL_WATCHDOG - uint32_t sleep_interval = external_watchdog.getIntervalMs()/1000; + external_watchdog.feed(); + uint32_t sleep_interval = external_watchdog.getIntervalMs() / 1000; board.sleep((sleep_interval > 1800) ? 1800 : sleep_interval); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet #else board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet diff --git a/src/helpers/ExternalWatchdogManager.h b/src/helpers/ExternalWatchdogManager.h index 9ef8abaa..cb4f4923 100644 --- a/src/helpers/ExternalWatchdogManager.h +++ b/src/helpers/ExternalWatchdogManager.h @@ -2,9 +2,9 @@ class ExternalWatchdogManager { protected: - unsigned long next_feed_watchdog; + unsigned long last_feed_watchdog; public: - ExternalWatchdogManager() { next_feed_watchdog = 0; } + ExternalWatchdogManager() { last_feed_watchdog = 0; } virtual bool begin() { return false; } virtual void loop() { } virtual unsigned long getIntervalMs() const { return 0; } diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index 1bc7e7fa..38594ecc 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -17,7 +17,7 @@ build_flags = ${nrf52_base.build_flags} -D HAS_EXTERNAL_WATCHDOG -D EXTERNAL_WATCHDOG_DONE_PIN=9 -D EXTERNAL_WATCHDOG_WAKE_PIN=10 - -D EXTERNAL_WATCHDOG_TIMEOUT_MS=480000 ;(6*60*1000) ; 6 minute watchdog + -D EXTERNAL_WATCHDOG_FEED_INTERVAL_MS=480000 ; 8 minute feed interval, safely inside the hardware watchdog timeout build_src_filter = ${nrf52_base.build_src_filter} + @@ -97,4 +97,4 @@ build_src_filter = ${Heltec_mesh_solar.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${Heltec_mesh_solar.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index a40b8ce9..96411c55 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -124,7 +124,7 @@ bool SolarSensorManager::setSettingValue(const char* name, const char* value) { } bool SolarExternalWatchdog::begin() { - next_feed_watchdog = 0; + last_feed_watchdog = 0; pinMode(EXTERNAL_WATCHDOG_WAKE_PIN, INPUT); pinMode(EXTERNAL_WATCHDOG_DONE_PIN, OUTPUT); delay(1); @@ -134,23 +134,22 @@ bool SolarExternalWatchdog::begin() { return true; } void SolarExternalWatchdog::loop() { - if (millis() > next_feed_watchdog) { + if (millis() - last_feed_watchdog >= EXTERNAL_WATCHDOG_FEED_INTERVAL_MS) { feed(); - next_feed_watchdog = millis() + EXTERNAL_WATCHDOG_TIMEOUT_MS; } } unsigned long SolarExternalWatchdog::getIntervalMs() const { - unsigned long interval_ms = 0; - interval_ms = next_feed_watchdog - millis(); - if(interval_ms > EXTERNAL_WATCHDOG_TIMEOUT_MS) { - interval_ms = EXTERNAL_WATCHDOG_TIMEOUT_MS; + unsigned long elapsed_ms = millis() - last_feed_watchdog; + if (elapsed_ms >= EXTERNAL_WATCHDOG_FEED_INTERVAL_MS) { + return 0; } - return interval_ms; + return EXTERNAL_WATCHDOG_FEED_INTERVAL_MS - elapsed_ms; } void SolarExternalWatchdog::feed() { digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, HIGH); delay(1); digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, LOW); + last_feed_watchdog = millis(); } From 8e787f6ee401e7671446bf05ce4839b614656252 Mon Sep 17 00:00:00 2001 From: jirogit Date: Tue, 21 Apr 2026 22:23:42 -0700 Subject: [PATCH 05/57] fix(RegionMap): save() returns actual write success instead of hardcoded true --- src/helpers/RegionMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 7b8399e2..36c1d19d 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -139,7 +139,7 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) { } } file.close(); - return true; + return success; } return false; // failed } From 3a546a6395c440a2c463283ef1b3e7d178e81836 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Tue, 12 May 2026 14:53:17 +0800 Subject: [PATCH 06/57] add heltec tower v2 --- boards/heltec_tower_v2.json | 61 +++++++++++ .../heltec_tower_v2/HeltecTowerV2Board.cpp | 102 +++++++++++++++++ variants/heltec_tower_v2/HeltecTowerV2Board.h | 23 ++++ variants/heltec_tower_v2/platformio.ini | 103 ++++++++++++++++++ variants/heltec_tower_v2/target.cpp | 31 ++++++ variants/heltec_tower_v2/target.h | 28 +++++ variants/heltec_tower_v2/variant.cpp | 25 +++++ variants/heltec_tower_v2/variant.h | 101 +++++++++++++++++ 8 files changed, 474 insertions(+) create mode 100644 boards/heltec_tower_v2.json create mode 100644 variants/heltec_tower_v2/HeltecTowerV2Board.cpp create mode 100644 variants/heltec_tower_v2/HeltecTowerV2Board.h create mode 100644 variants/heltec_tower_v2/platformio.ini create mode 100644 variants/heltec_tower_v2/target.cpp create mode 100644 variants/heltec_tower_v2/target.h create mode 100644 variants/heltec_tower_v2/variant.cpp create mode 100644 variants/heltec_tower_v2/variant.h diff --git a/boards/heltec_tower_v2.json b/boards/heltec_tower_v2.json new file mode 100644 index 00000000..9f9b6ab5 --- /dev/null +++ b/boards/heltec_tower_v2.json @@ -0,0 +1,61 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A","0x4405"], + ["0x239A","0x0029"], + ["0x239A","0x002A"], + ["0x239A","0x0071"] + ], + "usb_product": "HT-n5262", + "mcu": "nrf52840", + "variant": "heltec_tower_v2", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "Heltec Tower V2 Board", + "upload": { + "maximum_ram_size": 235520, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://heltec.org/", + "vendor": "Heltec" +} diff --git a/variants/heltec_tower_v2/HeltecTowerV2Board.cpp b/variants/heltec_tower_v2/HeltecTowerV2Board.cpp new file mode 100644 index 00000000..2fd7a618 --- /dev/null +++ b/variants/heltec_tower_v2/HeltecTowerV2Board.cpp @@ -0,0 +1,102 @@ +#include "HeltecTowerV2Board.h" + +#include +#include + +extern void variant_shutdown(); + +#ifdef NRF52_POWER_MANAGEMENT +const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK +}; + +void HeltecTowerV2Board::initiateShutdown(uint8_t reason) { + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE); + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, LOW); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); + + bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT); + pinMode(PIN_BAT_CTL, OUTPUT); + digitalWrite(PIN_BAT_CTL, enable_lpcomp ? HIGH : LOW); + + if (enable_lpcomp) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + variant_shutdown(); + enterSystemOff(reason); +} +#endif + +void HeltecTowerV2Board::begin() { + NRF52Board::begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); +#endif + + pinMode(PIN_BAT_CTL, OUTPUT); + digitalWrite(PIN_BAT_CTL, LOW); + +#ifdef NRF52_POWER_MANAGEMENT + checkBootVoltage(&power_config); +#endif + + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); + Wire.begin(); + + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, HIGH); +} + +#ifdef P_LORA_TX_LED +void HeltecTowerV2Board::onBeforeTransmit() { + digitalWrite(P_LORA_TX_LED, LED_STATE_ON); +} + +void HeltecTowerV2Board::onAfterTransmit() { + digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); +} +#endif + +uint16_t HeltecTowerV2Board::getBattMilliVolts() { + analogReadResolution(12); + analogReference(VBAT_AR_INTERNAL); + pinMode(PIN_VBAT_READ, INPUT); + pinMode(PIN_BAT_CTL, OUTPUT); + digitalWrite(PIN_BAT_CTL, HIGH); + + delay(10); + int adcvalue = analogRead(PIN_VBAT_READ); + digitalWrite(PIN_BAT_CTL, LOW); + + return (uint16_t)((float)adcvalue * MV_LSB * ADC_MULTIPLIER); +} + +const char* HeltecTowerV2Board::getManufacturerName() const { + return "Heltec Tower V2"; +} + +void HeltecTowerV2Board::powerOff() { + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE); + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, LOW); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); + pinMode(PIN_BAT_CTL, OUTPUT); + digitalWrite(PIN_BAT_CTL, LOW); + variant_shutdown(); + sd_power_system_off(); +} diff --git a/variants/heltec_tower_v2/HeltecTowerV2Board.h b/variants/heltec_tower_v2/HeltecTowerV2Board.h new file mode 100644 index 00000000..817d3c1a --- /dev/null +++ b/variants/heltec_tower_v2/HeltecTowerV2Board.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +class HeltecTowerV2Board : public NRF52BoardDCDC { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + +public: + HeltecTowerV2Board() : NRF52Board("TOWER_V2_OTA") {} + void begin(); +#ifdef P_LORA_TX_LED + void onBeforeTransmit() override; + void onAfterTransmit() override; +#endif + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override; + void powerOff() override; +}; diff --git a/variants/heltec_tower_v2/platformio.ini b/variants/heltec_tower_v2/platformio.ini new file mode 100644 index 00000000..2fac9fbc --- /dev/null +++ b/variants/heltec_tower_v2/platformio.ini @@ -0,0 +1,103 @@ +[Heltec_tower_v2] +extends = nrf52_base +board = heltec_tower_v2 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -D ENV_INCLUDE_GPS=1 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/heltec_tower_v2 + -D HELTEC_TOWER_V2 + -D NRF52_POWER_MANAGEMENT + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + + + +<../variants/heltec_tower_v2> +lib_deps = + ${nrf52_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 +debug_tool = jlink +upload_protocol = nrfutil + +[env:Heltec_tower_v2_repeater] +extends = Heltec_tower_v2 +build_src_filter = ${Heltec_tower_v2.build_src_filter} + +<../examples/simple_repeater> +build_flags = + ${Heltec_tower_v2.build_flags} + -D ADVERT_NAME='"Heltec_Tower_V2 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 + +[env:Heltec_tower_v2_room_server] +extends = Heltec_tower_v2 +build_src_filter = ${Heltec_tower_v2.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${Heltec_tower_v2.build_flags} + -D ADVERT_NAME='"Heltec_Tower_V2 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 + +[env:Heltec_tower_v2_companion_radio_ble] +extends = Heltec_tower_v2 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${Heltec_tower_v2.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tower_v2.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_tower_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_tower_v2_companion_radio_usb] +extends = Heltec_tower_v2 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${Heltec_tower_v2.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tower_v2.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_tower_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_tower_v2_kiss_modem] +extends = Heltec_tower_v2 +build_src_filter = ${Heltec_tower_v2.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_tower_v2/target.cpp b/variants/heltec_tower_v2/target.cpp new file mode 100644 index 00000000..ad457354 --- /dev/null +++ b/variants/heltec_tower_v2/target.cpp @@ -0,0 +1,31 @@ +#include "target.h" + +#include +#include +#include + +HeltecTowerV2Board board; + +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); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + return radio.std_init(&SPI); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} diff --git a/variants/heltec_tower_v2/target.h b/variants/heltec_tower_v2/target.h new file mode 100644 index 00000000..03719246 --- /dev/null +++ b/variants/heltec_tower_v2/target.h @@ -0,0 +1,28 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS +#include +#include "helpers/ui/NullDisplayDriver.h" +#endif + +extern HeltecTowerV2Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; +#endif + +bool radio_init(); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_tower_v2/variant.cpp b/variants/heltec_tower_v2/variant.cpp new file mode 100644 index 00000000..699c6e4a --- /dev/null +++ b/variants/heltec_tower_v2/variant.cpp @@ -0,0 +1,25 @@ +#include "variant.h" + +#include "Arduino.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() +{ + +} + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} diff --git a/variants/heltec_tower_v2/variant.h b/variants/heltec_tower_v2/variant.h new file mode 100644 index 00000000..d3f2599a --- /dev/null +++ b/variants/heltec_tower_v2/variant.h @@ -0,0 +1,101 @@ +#pragma once + +#include "WVariant.h" + +#define USE_LFXO +#define VARIANT_MCK (64000000ul) + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +#define WIRE_INTERFACES_COUNT (1) +#define PIN_WIRE_SDA (0 + 11) +#define PIN_WIRE_SCL (0 + 12) +#define PIN_BOARD_SDA PIN_WIRE_SDA +#define PIN_BOARD_SCL PIN_WIRE_SCL + +#define SPI_INTERFACES_COUNT (1) +#define PIN_SPI_MISO (0 + 23) +#define PIN_SPI_MOSI (0 + 22) +#define PIN_SPI_SCK (0 + 19) +#define PIN_SPI_NSS LORA_CS + +#define LED_BUILTIN (32 + 15) +#define PIN_LED LED_BUILTIN +#define LED_RED (-1) +#define LED_GREEN (-1) +#define LED_BLUE (-1) +#define LED_PIN (-1) +#define P_LORA_TX_LED LED_BUILTIN +#define LED_STATE_ON LOW + +#define PIN_BUTTON1 (32 + 10) +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN + +#define USE_SX1262 +#define SX126X_CS (0 + 24) +#define LORA_CS SX126X_CS +#define SX126X_DIO1 (0 + 20) +#define SX126X_BUSY (0 + 17) +#define SX126X_RESET (0 + 25) +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define P_LORA_NSS LORA_CS +#define P_LORA_DIO_1 SX126X_DIO1 +#define P_LORA_BUSY SX126X_BUSY +#define P_LORA_RESET SX126X_RESET +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI +#define P_LORA_SCLK PIN_SPI_SCK + +#define GPS_L76K +#define GPS_RESET_MODE LOW +#define PIN_GPS_RESET (32 + 6) +#define PIN_GPS_RESET_ACTIVE GPS_RESET_MODE +#define PIN_GPS_EN (0 + 7) +#define PIN_GPS_EN_ACTIVE LOW +#define GPS_EN_ACTIVE PIN_GPS_EN_ACTIVE +#define PIN_GPS_STANDBY (32 + 2) +#define PIN_GPS_PPS (32 + 4) +#define GPS_BAUD_RATE 9600 + +// Upstream names are from the GPS perspective. MeshCore's PIN_GPS_TX is the +// CPU RX pin because EnvironmentSensorManager passes it as Serial1 RX. +#define GPS_TX_PIN (32 + 7) +#define GPS_RX_PIN (32 + 5) +#define PIN_GPS_TX GPS_RX_PIN +#define PIN_GPS_RX GPS_TX_PIN + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) + +#define HAS_HARDWARE_WATCHDOG +#define HARDWARE_WATCHDOG_DONE (0 + 9) +#define HARDWARE_WATCHDOG_WAKE (0 + 10) +#define HARDWARE_WATCHDOG_TIMEOUT_MS (8 * 60 * 1000) + +#define SERIAL_PRINT_PORT 0 + +#define PIN_BAT_CTL (0 + 21) +#define ADC_CTRL PIN_BAT_CTL +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN (0 + 4) +#define PIN_VBAT_READ BATTERY_PIN +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER (4.916F) +#define MV_LSB (3000.0F / 4096.0F) + +#define NRF52_POWER_MANAGEMENT +#define PWRMGT_VOLTAGE_BOOTLOCK 3100 +#define PWRMGT_LPCOMP_AIN 2 +#define PWRMGT_LPCOMP_REFSEL 1 \ No newline at end of file From f29cae602f0f2e1102fb085023202ad6861620e1 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 16 May 2026 09:30:49 +0700 Subject: [PATCH 07/57] Added flash_mode=dio to avoid boot loop when flashing using merge.bin --- variants/xiao_c6/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 9f504b8e..0d8c2a79 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -1,6 +1,7 @@ [Xiao_C6] extends = esp32c6_base board = esp32-c6-devkitm-1 +board_build.flash_mode = dio board_build.partitions = min_spiffs.csv ; get around 4mb flash limit build_flags = ${esp32c6_base.build_flags} From 00d445a269451e2af0bd54365346c9a8b584f300 Mon Sep 17 00:00:00 2001 From: Sefinek Date: Thu, 28 May 2026 15:44:56 +0200 Subject: [PATCH 08/57] fix: fix typos in source code comments --- examples/companion_radio/DataStore.cpp | 2 +- src/Mesh.h | 2 +- src/Packet.h | 2 +- src/helpers/bridges/ESPNowBridge.cpp | 2 +- src/helpers/stm32/InternalFileSystem.cpp | 6 +++--- src/helpers/ui/OLEDDisplay.cpp | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index c7988bb3..dda0a84e 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -541,7 +541,7 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src uint32_t pos = 0, found_pos = 0; uint32_t min_timestamp = 0xFFFFFFFF; - // search for matching key OR evict by oldest timestmap + // search for matching key OR evict by oldest timestamp BlobRec tmp; file.seek(0); while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) { diff --git a/src/Mesh.h b/src/Mesh.h index d53d6d25..d3c1f73c 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -100,7 +100,7 @@ protected: * \param auth_code a code to authenticate the packet * \param flags zero for now * \param path_snrs single byte SNR*4 for each hop in the path - * \param path_hashes hashes if each repeater in the path + * \param path_hashes hashes of each repeater in the path * \param path_len length of the path_snrs[] and path_hashes[] arrays */ virtual void onTraceRecv(Packet* packet, uint32_t tag, uint32_t auth_code, uint8_t flags, const uint8_t* path_snrs, const uint8_t* path_hashes, uint8_t path_len) { } diff --git a/src/Packet.h b/src/Packet.h index 0886a06c..c19d9e9d 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -25,7 +25,7 @@ namespace mesh { #define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: data_type(uint16), data_len, blob) #define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...) #define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra) -#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop +#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNR for each hop #define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets #define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet //... diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index b9eb1c10..808e9df4 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -32,7 +32,7 @@ void ESPNowBridge::begin() { // Initialize WiFi in station mode WiFi.mode(WIFI_STA); - // Set wifi channel + // Set Wi-Fi channel if (esp_wifi_set_channel(_prefs->bridge_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) { BRIDGE_DEBUG_PRINTLN("Error setting WIFI channel to %d\n", _prefs->bridge_channel); return; diff --git a/src/helpers/stm32/InternalFileSystem.cpp b/src/helpers/stm32/InternalFileSystem.cpp index dc032eb9..6a7d7064 100644 --- a/src/helpers/stm32/InternalFileSystem.cpp +++ b/src/helpers/stm32/InternalFileSystem.cpp @@ -37,7 +37,7 @@ static int _internal_flash_read(const struct lfs_config *c, lfs_block_t block, l } // Program a region in a block. The block must have previously -// been erased. Negative error codes are propogated to the user. +// been erased. Negative error codes are propagated to the user. // May return LFS_ERR_CORRUPT if the block should be considered bad. static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { @@ -62,7 +62,7 @@ static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, l // Erase a block. A block must be erased before being programmed. // The state of an erased block is undefined. Negative error codes -// are propogated to the user. +// are propagated to the user. // May return LFS_ERR_CORRUPT if the block should be considered bad. static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block) { @@ -87,7 +87,7 @@ static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block) } // Sync the state of the underlying block device. Negative error codes -// are propogated to the user. +// are propagated to the user. static int _internal_flash_sync(const struct lfs_config *c) { return LFS_ERR_OK; // don't need sync diff --git a/src/helpers/ui/OLEDDisplay.cpp b/src/helpers/ui/OLEDDisplay.cpp index 19101344..aa11ce1a 100644 --- a/src/helpers/ui/OLEDDisplay.cpp +++ b/src/helpers/ui/OLEDDisplay.cpp @@ -1155,7 +1155,7 @@ void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { char DefaultFontTableLookup(const uint8_t ch) { // UTF-8 to font table index converter - // Code form http://playground.arduino.cc/Main/Utf8ascii + // Code from http://playground.arduino.cc/Main/Utf8ascii static uint8_t LASTCHAR; if (ch < 128) { // Standard ASCII-set 0..0x7F handling @@ -1166,7 +1166,7 @@ char DefaultFontTableLookup(const uint8_t ch) { uint8_t last = LASTCHAR; // get last char LASTCHAR = ch; - switch (last) { // conversion depnding on first UTF8-character + switch (last) { // conversion depending on first UTF8-character case 0xC2: return (uint8_t) ch; case 0xC3: return (uint8_t) (ch | 0xC0); case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol From 4bf391f5c38f4d65cc595e8ba747e1a2e5cebaa6 Mon Sep 17 00:00:00 2001 From: Sefinek Date: Thu, 28 May 2026 16:38:39 +0200 Subject: [PATCH 09/57] fix: fix typos in source code comments --- src/Mesh.h | 4 ++-- src/helpers/sensors/LPPDataHelpers.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mesh.h b/src/Mesh.h index d3c1f73c..a4967c1d 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -212,12 +212,12 @@ public: void sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis=0); /** - * \brief send a locally-generated Packet to just neigbor nodes (zero hops) + * \brief send a locally-generated Packet to just neighbor nodes (zero hops) */ void sendZeroHop(Packet* packet, uint32_t delay_millis=0); /** - * \brief send a locally-generated Packet to just neigbor nodes (zero hops), with specific transort codes + * \brief send a locally-generated Packet to just neighbor nodes (zero hops), with specific transport codes * \param transport_codes array of 2 codes to attach to packet */ void sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); diff --git a/src/helpers/sensors/LPPDataHelpers.h b/src/helpers/sensors/LPPDataHelpers.h index 37b50f3f..70a036c4 100644 --- a/src/helpers/sensors/LPPDataHelpers.h +++ b/src/helpers/sensors/LPPDataHelpers.h @@ -142,7 +142,7 @@ public: case LPP_GPS: _pos += 9; break; case LPP_POLYLINE: - _pos += 8; break; // TODO: this is MINIMIUM + _pos += 8; break; // TODO: this is MINIMUM case LPP_GYROMETER: case LPP_ACCELEROMETER: _pos += 6; break; From fe56d01da28b5bf552274cf946a147ffb558a876 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Fri, 12 Jun 2026 17:58:19 +0800 Subject: [PATCH 10/57] add tower v2 PA control --- .../heltec_tower_v2/HeltecTowerV2Board.cpp | 9 +++-- variants/heltec_tower_v2/HeltecTowerV2Board.h | 5 ++- variants/heltec_tower_v2/LoRaFEMControl.cpp | 40 +++++++++++++++++++ variants/heltec_tower_v2/LoRaFEMControl.h | 13 ++++++ variants/heltec_tower_v2/platformio.ini | 2 +- variants/heltec_tower_v2/variant.cpp | 15 +++++++ variants/heltec_tower_v2/variant.h | 13 ++++-- 7 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 variants/heltec_tower_v2/LoRaFEMControl.cpp create mode 100644 variants/heltec_tower_v2/LoRaFEMControl.h diff --git a/variants/heltec_tower_v2/HeltecTowerV2Board.cpp b/variants/heltec_tower_v2/HeltecTowerV2Board.cpp index 2fd7a618..04213bda 100644 --- a/variants/heltec_tower_v2/HeltecTowerV2Board.cpp +++ b/variants/heltec_tower_v2/HeltecTowerV2Board.cpp @@ -19,6 +19,7 @@ void HeltecTowerV2Board::initiateShutdown(uint8_t reason) { digitalWrite(PIN_GPS_STANDBY, LOW); pinMode(PIN_GPS_RESET, OUTPUT); digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); + loRaFEMControl.setSleepModeEnable(); bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || reason == SHUTDOWN_REASON_BOOT_PROTECT); @@ -37,10 +38,8 @@ void HeltecTowerV2Board::initiateShutdown(uint8_t reason) { void HeltecTowerV2Board::begin() { NRF52Board::begin(); -#ifdef P_LORA_TX_LED pinMode(P_LORA_TX_LED, OUTPUT); digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); -#endif pinMode(PIN_BAT_CTL, OUTPUT); digitalWrite(PIN_BAT_CTL, LOW); @@ -58,17 +57,18 @@ void HeltecTowerV2Board::begin() { digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, HIGH); + loRaFEMControl.init(); } -#ifdef P_LORA_TX_LED void HeltecTowerV2Board::onBeforeTransmit() { digitalWrite(P_LORA_TX_LED, LED_STATE_ON); + loRaFEMControl.setTxModeEnable(); } void HeltecTowerV2Board::onAfterTransmit() { digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); + loRaFEMControl.setRxModeEnable(); } -#endif uint16_t HeltecTowerV2Board::getBattMilliVolts() { analogReadResolution(12); @@ -95,6 +95,7 @@ void HeltecTowerV2Board::powerOff() { digitalWrite(PIN_GPS_STANDBY, LOW); pinMode(PIN_GPS_RESET, OUTPUT); digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); + loRaFEMControl.setSleepModeEnable(); pinMode(PIN_BAT_CTL, OUTPUT); digitalWrite(PIN_BAT_CTL, LOW); variant_shutdown(); diff --git a/variants/heltec_tower_v2/HeltecTowerV2Board.h b/variants/heltec_tower_v2/HeltecTowerV2Board.h index 817d3c1a..6912acde 100644 --- a/variants/heltec_tower_v2/HeltecTowerV2Board.h +++ b/variants/heltec_tower_v2/HeltecTowerV2Board.h @@ -3,6 +3,7 @@ #include #include #include +#include "LoRaFEMControl.h" class HeltecTowerV2Board : public NRF52BoardDCDC { protected: @@ -11,12 +12,12 @@ protected: #endif public: + LoRaFEMControl loRaFEMControl; + HeltecTowerV2Board() : NRF52Board("TOWER_V2_OTA") {} void begin(); -#ifdef P_LORA_TX_LED void onBeforeTransmit() override; void onAfterTransmit() override; -#endif uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override; void powerOff() override; diff --git a/variants/heltec_tower_v2/LoRaFEMControl.cpp b/variants/heltec_tower_v2/LoRaFEMControl.cpp new file mode 100644 index 00000000..d87d1849 --- /dev/null +++ b/variants/heltec_tower_v2/LoRaFEMControl.cpp @@ -0,0 +1,40 @@ +#include "LoRaFEMControl.h" + +#include +#include "variant.h" + +static void enableFEMPower() { + bool wasOff = digitalRead(LORA_KCT8103L_EN) != HIGH; + digitalWrite(LORA_KCT8103L_EN, HIGH); + if (wasOff) { + delay(5); + } +} + +void LoRaFEMControl::init() { + pinMode(LORA_KCT8103L_EN, OUTPUT); + digitalWrite(LORA_KCT8103L_EN, HIGH); + delay(1); + pinMode(LORA_KCT8103L_TX_RX, OUTPUT); + digitalWrite(LORA_KCT8103L_TX_RX, LOW); +} + +void LoRaFEMControl::setSleepModeEnable() { + pinMode(LORA_KCT8103L_EN, OUTPUT); + digitalWrite(LORA_KCT8103L_EN, LOW); +} + +void LoRaFEMControl::setTxModeEnable() { + enableFEMPower(); + digitalWrite(LORA_KCT8103L_TX_RX, HIGH); +} + +void LoRaFEMControl::setRxModeEnable() { + enableFEMPower(); + digitalWrite(LORA_KCT8103L_TX_RX, LOW); +} + +void LoRaFEMControl::setRxModeEnableWhenMCUSleep() { + enableFEMPower(); + digitalWrite(LORA_KCT8103L_TX_RX, LOW); +} diff --git a/variants/heltec_tower_v2/LoRaFEMControl.h b/variants/heltec_tower_v2/LoRaFEMControl.h new file mode 100644 index 00000000..e6c0ad2f --- /dev/null +++ b/variants/heltec_tower_v2/LoRaFEMControl.h @@ -0,0 +1,13 @@ +#pragma once + +class LoRaFEMControl { +public: + LoRaFEMControl() {} + virtual ~LoRaFEMControl() {} + + void init(); + void setSleepModeEnable(); + void setTxModeEnable(); + void setRxModeEnable(); + void setRxModeEnableWhenMCUSleep(); +}; diff --git a/variants/heltec_tower_v2/platformio.ini b/variants/heltec_tower_v2/platformio.ini index 2fac9fbc..190a21c9 100644 --- a/variants/heltec_tower_v2/platformio.ini +++ b/variants/heltec_tower_v2/platformio.ini @@ -11,7 +11,7 @@ build_flags = ${nrf52_base.build_flags} -D NRF52_POWER_MANAGEMENT -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper - -D LORA_TX_POWER=22 + -D LORA_TX_POWER=12 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52_base.build_src_filter} diff --git a/variants/heltec_tower_v2/variant.cpp b/variants/heltec_tower_v2/variant.cpp index 699c6e4a..66ec2111 100644 --- a/variants/heltec_tower_v2/variant.cpp +++ b/variants/heltec_tower_v2/variant.cpp @@ -19,7 +19,22 @@ void initVariant() void variant_shutdown() { + nrf_gpio_cfg_default(PIN_GPS_EN); nrf_gpio_cfg_default(PIN_GPS_PPS); + nrf_gpio_cfg_default(PIN_GPS_RESET); + nrf_gpio_cfg_default(PIN_GPS_STANDBY); + nrf_gpio_cfg_default(GPS_RX_PIN); + nrf_gpio_cfg_default(GPS_TX_PIN); + nrf_gpio_cfg_default(LORA_KCT8103L_TX_RX); + nrf_gpio_cfg_default(RF_PA_DETECT_PIN); + nrf_gpio_cfg_default(SX126X_CS); + nrf_gpio_cfg_default(SX126X_DIO1); + nrf_gpio_cfg_default(SX126X_BUSY); + nrf_gpio_cfg_default(SX126X_RESET); + nrf_gpio_cfg_default(PIN_SPI_MISO); + nrf_gpio_cfg_default(PIN_SPI_MOSI); + nrf_gpio_cfg_default(PIN_SPI_SCK); + nrf_gpio_cfg_default(PIN_LED); detachInterrupt(PIN_GPS_PPS); detachInterrupt(PIN_BUTTON1); } diff --git a/variants/heltec_tower_v2/variant.h b/variants/heltec_tower_v2/variant.h index d3f2599a..352184b8 100644 --- a/variants/heltec_tower_v2/variant.h +++ b/variants/heltec_tower_v2/variant.h @@ -11,8 +11,8 @@ #define NUM_ANALOG_OUTPUTS (0) #define WIRE_INTERFACES_COUNT (1) -#define PIN_WIRE_SDA (0 + 11) -#define PIN_WIRE_SCL (0 + 12) +#define PIN_WIRE_SDA (0 + 30) +#define PIN_WIRE_SCL (0 + 5) #define PIN_BOARD_SDA PIN_WIRE_SDA #define PIN_BOARD_SCL PIN_WIRE_SCL @@ -52,6 +52,13 @@ #define P_LORA_MOSI PIN_SPI_MOSI #define P_LORA_SCLK PIN_SPI_SCK +#define USE_KCT8103L_PA_ONLY +#define LORA_KCT8103L_EN (0 + 15) +#define LORA_KCT8103L_TX_RX (0 + 16) +#define LORA_PA_POWER LORA_KCT8103L_EN +#define RF_PA_DETECT_PIN (0 + 13) +#define RF_PA_HIGH_POWER_VALUE HIGH + #define GPS_L76K #define GPS_RESET_MODE LOW #define PIN_GPS_RESET (32 + 6) @@ -98,4 +105,4 @@ #define NRF52_POWER_MANAGEMENT #define PWRMGT_VOLTAGE_BOOTLOCK 3100 #define PWRMGT_LPCOMP_AIN 2 -#define PWRMGT_LPCOMP_REFSEL 1 \ No newline at end of file +#define PWRMGT_LPCOMP_REFSEL 1 From d2d3d44c9be7f2f6e90f191a7cb7a3c1d361fc75 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Fri, 12 Jun 2026 18:05:27 +0800 Subject: [PATCH 11/57] Add maximum power limit --- variants/heltec_tower_v2/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_tower_v2/platformio.ini b/variants/heltec_tower_v2/platformio.ini index 190a21c9..f029b7d4 100644 --- a/variants/heltec_tower_v2/platformio.ini +++ b/variants/heltec_tower_v2/platformio.ini @@ -12,6 +12,7 @@ build_flags = ${nrf52_base.build_flags} -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=12 + -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -> ~29dBm at antenna -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${nrf52_base.build_src_filter} From 1c4c995a41777781c390ff3a41f8a1c6eb221ee1 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Sat, 13 Jun 2026 16:00:06 +0800 Subject: [PATCH 12/57] Fix low power consumption issues --- variants/heltec_tower_v2/variant.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/heltec_tower_v2/variant.cpp b/variants/heltec_tower_v2/variant.cpp index 66ec2111..cfb88c4a 100644 --- a/variants/heltec_tower_v2/variant.cpp +++ b/variants/heltec_tower_v2/variant.cpp @@ -25,6 +25,8 @@ void variant_shutdown() nrf_gpio_cfg_default(PIN_GPS_STANDBY); nrf_gpio_cfg_default(GPS_RX_PIN); nrf_gpio_cfg_default(GPS_TX_PIN); + pinMode(LORA_KCT8103L_EN, OUTPUT); + digitalWrite(LORA_KCT8103L_EN, LOW); nrf_gpio_cfg_default(LORA_KCT8103L_TX_RX); nrf_gpio_cfg_default(RF_PA_DETECT_PIN); nrf_gpio_cfg_default(SX126X_CS); From e7db7c5a94680a1e983cf1c7aa2b558f85b72ce0 Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 14 Jun 2026 11:41:01 +0200 Subject: [PATCH 13/57] Add ESP32 Reset Reason --- src/helpers/CommonCLI.cpp | 4 ---- src/helpers/ESP32Board.h | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 5a5d1eab..25f1a2d1 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -944,13 +944,9 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, "ERROR: Power management not supported"); #endif } else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) { -#ifdef NRF52_POWER_MANAGEMENT sprintf(reply, "> Reset: %s; Shutdown: %s", _board->getResetReasonString(_board->getResetReason()), _board->getShutdownReasonString(_board->getShutdownReason())); -#else - strcpy(reply, "ERROR: Power management not supported"); -#endif } else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) { #ifdef NRF52_POWER_MANAGEMENT sprintf(reply, "> %u mV", _board->getBootVoltage()); diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index a4cbf2a9..1efc99f3 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -155,6 +155,42 @@ public: void setInhibitSleep(bool inhibit) { inhibit_sleep = inhibit; } + + uint32_t getResetReason() const override { + return esp_reset_reason(); + } + + // https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/system/system.html + const char* getResetReasonString(uint32_t reason) { + switch (reason) { + case ESP_RST_UNKNOWN: + return "Unknown or first boot"; + case ESP_RST_POWERON: + return "Power-on reset"; + case ESP_RST_EXT: + return "External reset"; + case ESP_RST_SW: + return "Software reset"; + case ESP_RST_PANIC: + return "Panic / exception reset"; + case ESP_RST_INT_WDT: + return "Interrupt watchdog reset"; + case ESP_RST_TASK_WDT: + return "Task watchdog reset"; + case ESP_RST_WDT: + return "Other watchdog reset"; + case ESP_RST_DEEPSLEEP: + return "Wake from deep sleep"; + case ESP_RST_BROWNOUT: + return "Brownout (low voltage)"; + case ESP_RST_SDIO: + return "SDIO reset"; + default: + static char buf[40]; + snprintf(buf, sizeof(buf), "Unknown reset reason (%d)", reason); + return buf; + } + } }; class ESP32RTCClock : public mesh::RTCClock { From dd9b3b32c39876ad6b45090e48ef185e3c83d461 Mon Sep 17 00:00:00 2001 From: taco Date: Mon, 15 Jun 2026 00:57:12 +1000 Subject: [PATCH 14/57] build fix for Heltec Mesh Solar and Mesh Pocket --- variants/heltec_mesh_solar/platformio.ini | 1 - variants/mesh_pocket/platformio.ini | 3 --- 2 files changed, 4 deletions(-) diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index 92d641a3..bd042322 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -1,7 +1,6 @@ [Heltec_mesh_solar] extends = nrf52_base board = heltec_mesh_solar -platform_packages = framework-arduinoadafruitnrf52 board_build.ldscript = boards/nrf52840_s140_v6.ld build_flags = ${nrf52_base.build_flags} -I src/helpers/nrf52 diff --git a/variants/mesh_pocket/platformio.ini b/variants/mesh_pocket/platformio.ini index 52a0d835..0d2a74ad 100644 --- a/variants/mesh_pocket/platformio.ini +++ b/variants/mesh_pocket/platformio.ini @@ -1,7 +1,6 @@ [Mesh_pocket] extends = nrf52_base board = heltec_mesh_pocket -platform_packages = framework-arduinoadafruitnrf52 board_build.ldscript = boards/nrf52840_s140_v6.ld build_flags = ${nrf52_base.build_flags} -I src/helpers/nrf52 @@ -32,7 +31,6 @@ lib_deps = stevemarple/MicroNMEA @ ^2.0.6 zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 - debug_tool = jlink upload_protocol = nrfutil @@ -40,7 +38,6 @@ upload_protocol = nrfutil extends = Mesh_pocket build_src_filter = ${Mesh_pocket.build_src_filter} +<../examples/simple_repeater> - build_flags = ${Mesh_pocket.build_flags} -D ADVERT_NAME='"Heltec_Mesh_Pocket Repeater"' From 62849ef1142369f76c0975d709a23288b5a9b861 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Mon, 22 Jun 2026 09:13:36 +0700 Subject: [PATCH 15/57] Fixed GPS time sync for Heltec T114 --- variants/heltec_t114/target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index cb8f75db..90a902f8 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -22,7 +22,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; From b975180fc88e441041ec52ffa49b36b0a215e1b0 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Thu, 25 Jun 2026 16:03:27 +1200 Subject: [PATCH 16/57] initial support for elecrow thinknode m7 --- boards/thinknode_m7.json | 42 +++++++ variants/thinknode_m7/ThinkNodeM7Board.cpp | 17 +++ variants/thinknode_m7/ThinkNodeM7Board.h | 23 ++++ variants/thinknode_m7/pins_arduino.h | 19 +++ variants/thinknode_m7/platformio.ini | 134 +++++++++++++++++++++ variants/thinknode_m7/target.cpp | 84 +++++++++++++ variants/thinknode_m7/target.h | 29 +++++ variants/thinknode_m7/variant.cpp | 8 ++ variants/thinknode_m7/variant.h | 1 + 9 files changed, 357 insertions(+) create mode 100644 boards/thinknode_m7.json create mode 100644 variants/thinknode_m7/ThinkNodeM7Board.cpp create mode 100644 variants/thinknode_m7/ThinkNodeM7Board.h create mode 100644 variants/thinknode_m7/pins_arduino.h create mode 100644 variants/thinknode_m7/platformio.ini create mode 100644 variants/thinknode_m7/target.cpp create mode 100644 variants/thinknode_m7/target.h create mode 100644 variants/thinknode_m7/variant.cpp create mode 100644 variants/thinknode_m7/variant.h diff --git a/boards/thinknode_m7.json b/boards/thinknode_m7.json new file mode 100644 index 00000000..2a0c5e58 --- /dev/null +++ b/boards/thinknode_m7.json @@ -0,0 +1,42 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-D BOARD_HAS_PSRAM", + "-D ARDUINO_USB_CDC_ON_BOOT=0", + "-D ARDUINO_USB_MODE=0", + "-D ARDUINO_RUNNING_CORE=1", + "-D ARDUINO_EVENT_RUNNING_CORE=0" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "qio_opi", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "ELECROW-ThinkNode-M7" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "ELECROW ThinkNode M7", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 524288, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.elecrow.com", + "vendor": "ELECROW" +} diff --git a/variants/thinknode_m7/ThinkNodeM7Board.cpp b/variants/thinknode_m7/ThinkNodeM7Board.cpp new file mode 100644 index 00000000..6b1bf860 --- /dev/null +++ b/variants/thinknode_m7/ThinkNodeM7Board.cpp @@ -0,0 +1,17 @@ +#include "ThinkNodeM7Board.h" + +void ThinkNodeM7Board::begin() { + ESP32Board::begin(); +} + +void ThinkNodeM7Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_deep_sleep_start(); +} + +void ThinkNodeM7Board::powerOff() { + enterDeepSleep(0); +} + +const char* ThinkNodeM7Board::getManufacturerName() const { + return "Elecrow ThinkNode M7"; +} diff --git a/variants/thinknode_m7/ThinkNodeM7Board.h b/variants/thinknode_m7/ThinkNodeM7Board.h new file mode 100644 index 00000000..f591b5ac --- /dev/null +++ b/variants/thinknode_m7/ThinkNodeM7Board.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include "variant.h" +#include "NullDisplayDriver.h" +#include "MomentaryButton.h" + +class ThinkNodeM7Board : public ESP32Board { + +public: + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + const char* getManufacturerName() const override; + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); + } +}; diff --git a/variants/thinknode_m7/pins_arduino.h b/variants/thinknode_m7/pins_arduino.h new file mode 100644 index 00000000..845a8db1 --- /dev/null +++ b/variants/thinknode_m7/pins_arduino.h @@ -0,0 +1,19 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SDA = 17; +static const uint8_t SCL = 18; + +// Default SPI is the LR1110 radio bus +static const uint8_t SS = 12; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 9; +static const uint8_t SCK = 11; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/thinknode_m7/platformio.ini b/variants/thinknode_m7/platformio.ini new file mode 100644 index 00000000..ea421b07 --- /dev/null +++ b/variants/thinknode_m7/platformio.ini @@ -0,0 +1,134 @@ +[ThinkNode_M7] +extends = esp32_base +board = thinknode_m7 +build_flags = ${esp32_base.build_flags} + -I src/helpers/esp32 + -I variants/thinknode_m7 + -I src/helpers/sensors + -I src/helpers/ui + -D THINKNODE_M7 + -D PIN_USER_BTN_ANA=4 + -D PIN_STATUS_LED=3 ; green + -D LED_STATE_ON=LOW + -D RADIO_CLASS=CustomLR1110 + -D WRAPPER_CLASS=CustomLR1110Wrapper + -D USE_LR1110 + -D LORA_TX_POWER=22 + -D RF_SWITCH_TABLE + -D RX_BOOSTED_GAIN=true + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=11 + -D P_LORA_NSS=12 + -D P_LORA_DIO_1=38 + -D P_LORA_MISO=9 + -D P_LORA_MOSI=10 + -D P_LORA_RESET=39 + -D P_LORA_TX_LED=46 ; blue + -D LR11X0_DIO_AS_RF_SWITCH=true + -D LR11X0_DIO3_TCXO_VOLTAGE=1.8 +build_src_filter = ${esp32_base.build_src_filter} + + + + + + + +<../variants/thinknode_m7> +lib_deps = ${esp32_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:ThinkNode_M7_repeater] +extends = ThinkNode_M7 +build_src_filter = ${ThinkNode_M7.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M7.build_flags} + -D ADVERT_NAME='"ThinkNode M7 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M7.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M7_room_server] +extends = ThinkNode_M7 +build_src_filter = ${ThinkNode_M7.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${ThinkNode_M7.build_flags} + -D ADVERT_NAME='"ThinkNode M7 Room Server"' + -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 +lib_deps = + ${ThinkNode_M7.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M7_companion_radio_ble] +extends = ThinkNode_M7 +build_flags = + ${ThinkNode_M7.build_flags} + -I examples/companion_radio/ui-orig + -D DISPLAY_CLASS=NullDisplayDriver + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + ; -D BLE_DEBUG_LOGGING=1 + ; -D MESH_PACKET_LOGGING=1 +build_src_filter = ${ThinkNode_M7.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = + ${ThinkNode_M7.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M7_companion_radio_usb] +extends = ThinkNode_M7 +build_flags = + ${ThinkNode_M7.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${ThinkNode_M7.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = + ${ThinkNode_M7.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M7_companion_radio_wifi] +extends = ThinkNode_M7 +build_flags = + ${ThinkNode_M7.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=NullDisplayDriver + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 + -D MESH_PACKET_LOGGING=1 +build_src_filter = ${ThinkNode_M7.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = + ${ThinkNode_M7.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M7_kiss_modem] +extends = ThinkNode_M7 +build_src_filter = ${ThinkNode_M7.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/thinknode_m7/target.cpp b/variants/thinknode_m7/target.cpp new file mode 100644 index 00000000..f4b898d8 --- /dev/null +++ b/variants/thinknode_m7/target.cpp @@ -0,0 +1,84 @@ +#include +#include "target.h" +#include + +ThinkNodeM7Board 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); + +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + +#ifdef RF_SWITCH_TABLE +static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC, + RADIOLIB_NC, + 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, {HIGH, 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, + END_OF_MODE_TABLE, +}; +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + +#ifdef LR11X0_DIO3_TCXO_VOLTAGE + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, P_LORA_NSS); + + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(2); + radio.explicitHeader(); + +#ifdef RF_SWITCH_TABLE + radio.setRfSwitchTable(rfswitch_dios, rfswitch_table); +#endif +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + + return true; // success +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + + diff --git a/variants/thinknode_m7/target.h b/variants/thinknode_m7/target.h new file mode 100644 index 00000000..34213f57 --- /dev/null +++ b/variants/thinknode_m7/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include "NullDisplayDriver.h" +#endif + +extern ThinkNodeM7Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern NullDisplayDriver display; +#endif + +bool radio_init(); +mesh::LocalIdentity radio_new_identity(); + + + \ No newline at end of file diff --git a/variants/thinknode_m7/variant.cpp b/variants/thinknode_m7/variant.cpp new file mode 100644 index 00000000..e056ab64 --- /dev/null +++ b/variants/thinknode_m7/variant.cpp @@ -0,0 +1,8 @@ +#include "variant.h" +#include "Arduino.h" + +void initVariant() +{ + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +} \ No newline at end of file diff --git a/variants/thinknode_m7/variant.h b/variants/thinknode_m7/variant.h new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/variants/thinknode_m7/variant.h @@ -0,0 +1 @@ + From 5d0ff7fe6e402704dd8c3ad10199ca8054c731b2 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 26 Jun 2026 07:06:37 +1000 Subject: [PATCH 17/57] remove guard on MyMesh::setRxBoostedGain() --- examples/simple_repeater/MyMesh.cpp | 2 -- examples/simple_repeater/MyMesh.h | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5cc3a9a1..5f928452 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1062,11 +1062,9 @@ void MyMesh::setTxPower(int8_t power_dbm) { radio_driver.setTxPower(power_dbm); } -#if defined(USE_SX1262) || defined(USE_SX1268) void MyMesh::setRxBoostedGain(bool enable) { radio_driver.setRxBoostedGainMode(enable); } -#endif void MyMesh::formatNeighborsReply(char *reply) { char *dp = reply; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 24c4b1f2..e66f7e59 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -252,7 +252,6 @@ public: // To check if there is pending work bool hasPendingWork() const; -#if defined(USE_SX1262) || defined(USE_SX1268) void setRxBoostedGain(bool enable) override; -#endif + }; From 9c7d71b9be9115e776d7aae28030d09023813a84 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Mon, 8 Dec 2025 11:21:29 +0100 Subject: [PATCH 18/57] Added support for the nibble zero connect --- variants/nibble_zero_connect/platformio.ini | 163 ++++++++++++++++++++ variants/nibble_zero_connect/target.cpp | 50 ++++++ variants/nibble_zero_connect/target.h | 31 ++++ 3 files changed, 244 insertions(+) create mode 100644 variants/nibble_zero_connect/platformio.ini create mode 100644 variants/nibble_zero_connect/target.cpp create mode 100644 variants/nibble_zero_connect/target.h diff --git a/variants/nibble_zero_connect/platformio.ini b/variants/nibble_zero_connect/platformio.ini new file mode 100644 index 00000000..4e40ff09 --- /dev/null +++ b/variants/nibble_zero_connect/platformio.ini @@ -0,0 +1,163 @@ +[nibble_zero_connect_base] +extends = esp32_base +board = esp32-s3-zero +build_flags = + ${esp32_base.build_flags} + -I variants/nibble_zero_connect + -D NIBBLE_ZERO_CONNECT + -D P_LORA_DIO_1=4 + -D P_LORA_NSS=10 + -D P_LORA_RESET=6 + -D P_LORA_BUSY=5 + -D P_LORA_SCLK=12 + -D P_LORA_MISO=13 + -D P_LORA_MOSI=11 + -D PIN_USER_BTN=1 + -D PIN_BOARD_SDA=8 + -D PIN_BOARD_SCL=7 + -D PIN_STATUS_LED=39 + -D P_LORA_TX_LED=39 + -D DISPLAY_ROTATION=0 + -D HAS_NEOPIXEL + -D NEOPIXEL_COUNT=1 + -D NEOPIXEL_DATA=17 + -D NEOPIXEL_TYPE=(NEO_GRB+NEO_KHZ800) + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/nibble_zero_connect> +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + adafruit/Adafruit NeoPixel @ ^1.12.3 + +[env:nibble_zero_connect_repeater] +extends = nibble_zero_connect_base +build_flags = + ${nibble_zero_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Nibble Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter = ${nibble_zero_connect_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${nibble_zero_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_zero_connect_repeater_bridge_espnow] +extends = nibble_zero_connect_base +build_flags = + ${nibble_zero_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${nibble_zero_connect_base.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${nibble_zero_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_zero_connect_terminal_chat] +extends = nibble_zero_connect_base +build_flags = + ${nibble_zero_connect_base.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=1 +build_src_filter = ${nibble_zero_connect_base.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${nibble_zero_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_zero_connect_room_server] +extends = nibble_zero_connect_base +build_flags = + ${nibble_zero_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Nibble Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${nibble_zero_connect_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${nibble_zero_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_zero_connect_companion_radio_usb] +extends = nibble_zero_connect_base +build_flags = + ${nibble_zero_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +build_src_filter = ${nibble_zero_connect_base.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_zero_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_zero_connect_companion_radio_ble] +extends = nibble_zero_connect_base +build_flags = + ${nibble_zero_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${nibble_zero_connect_base.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_zero_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_zero_connect_companion_radio_wifi] +extends = nibble_zero_connect_base +build_flags = + ${nibble_zero_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${nibble_zero_connect_base.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_zero_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + + diff --git a/variants/nibble_zero_connect/target.cpp b/variants/nibble_zero_connect/target.cpp new file mode 100644 index 00000000..73ac0a84 --- /dev/null +++ b/variants/nibble_zero_connect/target.cpp @@ -0,0 +1,50 @@ +#include +#include "target.h" + +ESP32Board 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; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + return radio.std_init(&spi); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} + + diff --git a/variants/nibble_zero_connect/target.h b/variants/nibble_zero_connect/target.h new file mode 100644 index 00000000..ea558f63 --- /dev/null +++ b/variants/nibble_zero_connect/target.h @@ -0,0 +1,31 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ESP32Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + + From 69df5c7a9588b6fcb436f474d63179074ea08abb Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Thu, 5 Feb 2026 08:51:16 +0100 Subject: [PATCH 19/57] Remove NeoPixel configuration as its unused. --- variants/nibble_zero_connect/platformio.ini | 5 ----- 1 file changed, 5 deletions(-) diff --git a/variants/nibble_zero_connect/platformio.ini b/variants/nibble_zero_connect/platformio.ini index 4e40ff09..14801920 100644 --- a/variants/nibble_zero_connect/platformio.ini +++ b/variants/nibble_zero_connect/platformio.ini @@ -18,10 +18,6 @@ build_flags = -D PIN_STATUS_LED=39 -D P_LORA_TX_LED=39 -D DISPLAY_ROTATION=0 - -D HAS_NEOPIXEL - -D NEOPIXEL_COUNT=1 - -D NEOPIXEL_DATA=17 - -D NEOPIXEL_TYPE=(NEO_GRB+NEO_KHZ800) -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 @@ -34,7 +30,6 @@ build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 - adafruit/Adafruit NeoPixel @ ^1.12.3 [env:nibble_zero_connect_repeater] extends = nibble_zero_connect_base From eb938a9b78f1d8b4b46d694a955115fc001bcd6e Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Tue, 12 May 2026 09:01:38 +0200 Subject: [PATCH 20/57] Address review comments and sync with dev --- variants/nibble_zero_connect/platformio.ini | 14 +++++++------- variants/nibble_zero_connect/target.cpp | 17 +---------------- variants/nibble_zero_connect/target.h | 3 --- 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/variants/nibble_zero_connect/platformio.ini b/variants/nibble_zero_connect/platformio.ini index 14801920..83495cd8 100644 --- a/variants/nibble_zero_connect/platformio.ini +++ b/variants/nibble_zero_connect/platformio.ini @@ -31,7 +31,7 @@ lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 -[env:nibble_zero_connect_repeater] +[env:nibble_zero_connect_repeater_] extends = nibble_zero_connect_base build_flags = ${nibble_zero_connect_base.build_flags} @@ -48,7 +48,7 @@ lib_deps = ${nibble_zero_connect_base.lib_deps} ${esp32_ota.lib_deps} -[env:nibble_zero_connect_repeater_bridge_espnow] +[env:nibble_zero_connect_repeater_bridge_espnow_] extends = nibble_zero_connect_base build_flags = ${nibble_zero_connect_base.build_flags} @@ -67,7 +67,7 @@ lib_deps = ${nibble_zero_connect_base.lib_deps} ${esp32_ota.lib_deps} -[env:nibble_zero_connect_terminal_chat] +[env:nibble_zero_connect_terminal_chat_] extends = nibble_zero_connect_base build_flags = ${nibble_zero_connect_base.build_flags} @@ -79,7 +79,7 @@ lib_deps = ${nibble_zero_connect_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:nibble_zero_connect_room_server] +[env:nibble_zero_connect_room_server_] extends = nibble_zero_connect_base build_flags = ${nibble_zero_connect_base.build_flags} @@ -96,7 +96,7 @@ lib_deps = ${nibble_zero_connect_base.lib_deps} ${esp32_ota.lib_deps} -[env:nibble_zero_connect_companion_radio_usb] +[env:nibble_zero_connect_companion_radio_usb_] extends = nibble_zero_connect_base build_flags = ${nibble_zero_connect_base.build_flags} @@ -113,7 +113,7 @@ lib_deps = ${nibble_zero_connect_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:nibble_zero_connect_companion_radio_ble] +[env:nibble_zero_connect_companion_radio_ble_] extends = nibble_zero_connect_base build_flags = ${nibble_zero_connect_base.build_flags} @@ -134,7 +134,7 @@ lib_deps = ${nibble_zero_connect_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:nibble_zero_connect_companion_radio_wifi] +[env:nibble_zero_connect_companion_radio_wifi_] extends = nibble_zero_connect_base build_flags = ${nibble_zero_connect_base.build_flags} diff --git a/variants/nibble_zero_connect/target.cpp b/variants/nibble_zero_connect/target.cpp index 73ac0a84..d53b6a1c 100644 --- a/variants/nibble_zero_connect/target.cpp +++ b/variants/nibble_zero_connect/target.cpp @@ -13,7 +13,7 @@ SensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; - MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif #ifndef LORA_CR @@ -27,21 +27,6 @@ bool radio_init() { return radio.std_init(&spi); } -uint32_t radio_get_rng_seed() { - return radio.random(0x7FFFFFFF); -} - -void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { - radio.setFrequency(freq); - radio.setSpreadingFactor(sf); - radio.setBandwidth(bw); - radio.setCodingRate(cr); -} - -void radio_set_tx_power(uint8_t dbm) { - radio.setOutputPower(dbm); -} - mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); diff --git a/variants/nibble_zero_connect/target.h b/variants/nibble_zero_connect/target.h index ea558f63..87c55f0f 100644 --- a/variants/nibble_zero_connect/target.h +++ b/variants/nibble_zero_connect/target.h @@ -23,9 +23,6 @@ extern SensorManager sensors; #endif bool radio_init(); -uint32_t radio_get_rng_seed(); -void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); mesh::LocalIdentity radio_new_identity(); From b8f1fad65446bb22803b6a74b7bbbd6a99a6f761 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Thu, 25 Jun 2026 23:26:53 +0200 Subject: [PATCH 21/57] Add trailing underscore to nibble_screen_connect env names to exclude from automatic builds --- variants/nibble_screen_connect/platformio.ini | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini index 20f7dc24..6a2f3dec 100644 --- a/variants/nibble_screen_connect/platformio.ini +++ b/variants/nibble_screen_connect/platformio.ini @@ -34,7 +34,7 @@ lib_deps = adafruit/Adafruit SSD1306 @ ^2.5.13 adafruit/Adafruit NeoPixel @ ^1.12.3 -[env:nibble_screen_connect_repeater] +[env:nibble_screen_connect_repeater_] extends = nibble_screen_connect_base build_flags = ${nibble_screen_connect_base.build_flags} @@ -51,7 +51,7 @@ lib_deps = ${nibble_screen_connect_base.lib_deps} ${esp32_ota.lib_deps} -[env:nibble_screen_connect_repeater_bridge_espnow] +[env:nibble_screen_connect_repeater_bridge_espnow_] extends = nibble_screen_connect_base build_flags = ${nibble_screen_connect_base.build_flags} @@ -70,7 +70,7 @@ lib_deps = ${nibble_screen_connect_base.lib_deps} ${esp32_ota.lib_deps} -[env:nibble_screen_connect_terminal_chat] +[env:nibble_screen_connect_terminal_chat_] extends = nibble_screen_connect_base build_flags = ${nibble_screen_connect_base.build_flags} @@ -82,7 +82,7 @@ lib_deps = ${nibble_screen_connect_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:nibble_screen_connect_room_server] +[env:nibble_screen_connect_room_server_] extends = nibble_screen_connect_base build_flags = ${nibble_screen_connect_base.build_flags} @@ -99,7 +99,7 @@ lib_deps = ${nibble_screen_connect_base.lib_deps} ${esp32_ota.lib_deps} -[env:nibble_screen_connect_companion_radio_usb] +[env:nibble_screen_connect_companion_radio_usb_] extends = nibble_screen_connect_base build_flags = ${nibble_screen_connect_base.build_flags} @@ -116,7 +116,7 @@ lib_deps = ${nibble_screen_connect_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:nibble_screen_connect_companion_radio_ble] +[env:nibble_screen_connect_companion_radio_ble_] extends = nibble_screen_connect_base build_flags = ${nibble_screen_connect_base.build_flags} @@ -137,7 +137,7 @@ lib_deps = ${nibble_screen_connect_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:nibble_screen_connect_companion_radio_wifi] +[env:nibble_screen_connect_companion_radio_wifi_] extends = nibble_screen_connect_base build_flags = ${nibble_screen_connect_base.build_flags} @@ -160,7 +160,7 @@ lib_deps = densaugeo/base64 @ ~1.4.0 -[env:nibble_screen_connect_kiss_modem] +[env:nibble_screen_connect_kiss_modem_] extends = nibble_screen_connect_base build_src_filter = ${nibble_screen_connect_base.build_src_filter} +<../examples/kiss_modem/> From 1f9bb6740048c08708718e3b26b628af98fe3ff1 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 26 Jun 2026 07:44:01 +1000 Subject: [PATCH 22/57] return bool when setting rx boost --- examples/simple_repeater/MyMesh.cpp | 4 ++-- examples/simple_repeater/MyMesh.h | 2 +- src/helpers/CommonCLI.h | 4 ++-- src/helpers/esp32/ESPNOWRadio.h | 2 +- src/helpers/radiolib/CustomLLCC68Wrapper.h | 4 ++-- src/helpers/radiolib/CustomLR1110Wrapper.h | 4 ++-- src/helpers/radiolib/CustomSX1262Wrapper.h | 4 ++-- src/helpers/radiolib/CustomSX1268Wrapper.h | 4 ++-- src/helpers/radiolib/RadioLibWrappers.h | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5f928452..ca4cfad2 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1062,8 +1062,8 @@ void MyMesh::setTxPower(int8_t power_dbm) { radio_driver.setTxPower(power_dbm); } -void MyMesh::setRxBoostedGain(bool enable) { - radio_driver.setRxBoostedGainMode(enable); +bool MyMesh::setRxBoostedGain(bool enable) { + return radio_driver.setRxBoostedGainMode(enable); } void MyMesh::formatNeighborsReply(char *reply) { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index e66f7e59..fb091a4c 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -252,6 +252,6 @@ public: // To check if there is pending work bool hasPendingWork() const; - void setRxBoostedGain(bool enable) override; + bool setRxBoostedGain(bool enable) override; }; diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 10cb00c7..f3abcf47 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -111,8 +111,8 @@ public: // no op by default }; - virtual void setRxBoostedGain(bool enable) { - // no op by default + virtual bool setRxBoostedGain(bool enable) { + return false; // CommonCLI reports unsupported if not overridden by wrapper }; }; diff --git a/src/helpers/esp32/ESPNOWRadio.h b/src/helpers/esp32/ESPNOWRadio.h index 67b1448e..f474215a 100644 --- a/src/helpers/esp32/ESPNOWRadio.h +++ b/src/helpers/esp32/ESPNOWRadio.h @@ -38,7 +38,7 @@ public: * These two functions do nothing for ESP-NOW, but are needed for the * Radio interface. */ - virtual void setRxBoostedGainMode(bool) { } + virtual bool setRxBoostedGainMode(bool) { } virtual bool getRxBoostedGainMode() const { return false; } uint32_t intID(); diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index 8861f76d..851fd644 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -33,8 +33,8 @@ public: void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } - void setRxBoostedGainMode(bool en) override { - ((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en); + bool setRxBoostedGainMode(bool en) override { + return ((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; } bool getRxBoostedGainMode() const override { return ((CustomLLCC68 *)_radio)->getRxBoostedGainMode(); diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index 13efd25b..fc505283 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -36,8 +36,8 @@ public: uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); } - void setRxBoostedGainMode(bool en) override { - ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en); + bool setRxBoostedGainMode(bool en) override { + return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; } bool getRxBoostedGainMode() const override { return ((CustomLR1110 *)_radio)->getRxBoostedGainMode(); diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index cc7bb223..1d103f57 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -40,8 +40,8 @@ public: void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } - void setRxBoostedGainMode(bool en) override { - ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en); + bool setRxBoostedGainMode(bool en) override { + return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; } bool getRxBoostedGainMode() const override { return ((CustomSX1262 *)_radio)->getRxBoostedGainMode(); diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 9ddea78f..bce56b99 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -37,8 +37,8 @@ public: void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } - void setRxBoostedGainMode(bool en) override { - ((CustomSX1268 *)_radio)->setRxBoostedGainMode(en); + bool setRxBoostedGainMode(bool en) override { + return ((CustomSX1268 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; } bool getRxBoostedGainMode() const override { return ((CustomSX1268 *)_radio)->getRxBoostedGainMode(); diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 9943bcab..3091832f 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -66,7 +66,7 @@ public: float packetScore(float snr, int packet_len) override { return packetScoreInt(snr, 10, packet_len); } // assume sf=10 - virtual void setRxBoostedGainMode(bool) { } + virtual bool setRxBoostedGainMode(bool) { return false; } virtual bool getRxBoostedGainMode() const { return false; } }; From b07aba793724e24b52138ea2a7172d9a3bd34e91 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 26 Jun 2026 08:45:03 +1000 Subject: [PATCH 23/57] fix: report error when rxgain setting is unsupported or rejected --- src/helpers/CommonCLI.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index dce1c5d8..f82d40e2 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -571,13 +571,15 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; savePrefs(); strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); -#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110) } else if (memcmp(config, "radio.rxgain ", 13) == 0) { - _prefs->rx_boosted_gain = memcmp(&config[13], "on", 2) == 0; - strcpy(reply, "OK"); - savePrefs(); - _callbacks->setRxBoostedGain(_prefs->rx_boosted_gain); -#endif + bool enabled = memcmp(&config[13], "on", 2) == 0; + if (_callbacks->setRxBoostedGain(enabled)) { + _prefs->rx_boosted_gain = enabled; + strcpy(reply, "OK"); + savePrefs(); + } else { + strcpy(reply, "Error: unsupported or rejected"); + } } else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) { if (!_board->canControlLoRaFemLna()) { strcpy(reply, "Error: unsupported"); @@ -835,10 +837,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat)); } else if (memcmp(config, "lon", 3) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon)); -#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110) } else if (memcmp(config, "radio.rxgain", 12) == 0) { sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off"); -#endif } else if (memcmp(config, "radio.fem.rxgain", 16) == 0) { if (!_board->canControlLoRaFemLna()) { strcpy(reply, "Error: unsupported"); From f3d4d8cd5eda7428435a1a7769e3891cdab67344 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 26 Jun 2026 22:36:49 +1000 Subject: [PATCH 24/57] always save boosted gain setting --- src/helpers/CommonCLI.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index f82d40e2..c95e3e34 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -573,13 +573,13 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON"); } else if (memcmp(config, "radio.rxgain ", 13) == 0) { bool enabled = memcmp(&config[13], "on", 2) == 0; + _prefs->rx_boosted_gain = enabled; + savePrefs(); if (_callbacks->setRxBoostedGain(enabled)) { - _prefs->rx_boosted_gain = enabled; strcpy(reply, "OK"); - savePrefs(); } else { - strcpy(reply, "Error: unsupported or rejected"); - } + strcpy(reply, "Error: unsupported"); + } } else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) { if (!_board->canControlLoRaFemLna()) { strcpy(reply, "Error: unsupported"); From 4f8cb8db78c908bc350cd990f116a3ff7d4b9976 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 27 Jun 2026 21:06:01 +1000 Subject: [PATCH 25/57] * ACK packets being 'clipped' (in Dispatcher send). Needed to extend max_airtime timeout calc for short packets --- src/helpers/radiolib/CustomLR1110Wrapper.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index fc505283..c6b1acb4 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -26,6 +26,11 @@ public: return rssi; } + uint32_t getEstAirtimeFor(int len_bytes) override { + auto airtime = RadioLibWrapper::getEstAirtimeFor(len_bytes); + return airtime < 200 ? 200 : airtime; // at least 200 millis + } + void onSendFinished() override { RadioLibWrapper::onSendFinished(); _radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts From bc35208682126f55f2ceae7c55ce8237d3ec51e2 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Sun, 28 Jun 2026 00:32:22 +0300 Subject: [PATCH 26/57] Update number_allocations.md Added Data-Type range for MeshCore Images codec (range is for small reserve to future versions) --- docs/number_allocations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/number_allocations.md b/docs/number_allocations.md index 857525d4..f35f3b2d 100644 --- a/docs/number_allocations.md +++ b/docs/number_allocations.md @@ -17,6 +17,7 @@ Once you have a working app/project, you need to be able to demonstrate it exist | 0000 - 00FF | -reserved for internal use- | | | 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open | | 0110 - 011F | Ripple | ripple_biz@protonmail.com — https://buymeacoffee.com/ripplebiz | +| 0120 - 0121 | MCOimg | most.original.address@gmail.com — https://hdden.ru/MCOimg/ | | FF00 - FFFF | -reserved for testing/dev- | | (add rows, inside the range 0100 - FEFF for custom apps) From 58b0f7df9cce97987540c84454131e090bb00dd7 Mon Sep 17 00:00:00 2001 From: HDDen <62592944+HDDen@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:41:44 +0300 Subject: [PATCH 27/57] Update number_allocations.md --- docs/number_allocations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/number_allocations.md b/docs/number_allocations.md index f35f3b2d..2ccf950f 100644 --- a/docs/number_allocations.md +++ b/docs/number_allocations.md @@ -17,7 +17,7 @@ Once you have a working app/project, you need to be able to demonstrate it exist | 0000 - 00FF | -reserved for internal use- | | | 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open | | 0110 - 011F | Ripple | ripple_biz@protonmail.com — https://buymeacoffee.com/ripplebiz | -| 0120 - 0121 | MCOimg | most.original.address@gmail.com — https://hdden.ru/MCOimg/ | +| 0120 | MCO Advanced | most.original.address@gmail.com — https://hdden.ru/MCOa/ | | FF00 - FFFF | -reserved for testing/dev- | | (add rows, inside the range 0100 - FEFF for custom apps) From 2cb3a29fa86971fceab26ac72f229316b40b474b Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:55:16 +1000 Subject: [PATCH 28/57] SX1262 LDO Support - Added build flag "SX126X_USE_REGULATOR_LDO" (set to 1 to enable) to use LDO for the SX1262 radio instead of DC-DC when the DCDC pin isn't wired up (e.g. t-echo lite) - Added additional debug mode diagnostic messages during SX1262 init to better highlight faults --- src/helpers/radiolib/CustomSX1262.h | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/CustomSX1262.h b/src/helpers/radiolib/CustomSX1262.h index ad201229..ca62fc26 100644 --- a/src/helpers/radiolib/CustomSX1262.h +++ b/src/helpers/radiolib/CustomSX1262.h @@ -1,6 +1,7 @@ #pragma once #include +#include "MeshCore.h" #define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received #define SX126X_IRQ_PREAMBLE_DETECTED 0x04 @@ -27,6 +28,14 @@ class CustomSX1262 : public SX1262 { uint8_t cr = 5; #endif + #ifdef SX126X_USE_REGULATOR_LDO + constexpr bool useRegulatorLDO = SX126X_USE_REGULATOR_LDO; + #else + constexpr bool useRegulatorLDO = false; + #endif + + MESH_DEBUG_PRINTLN("SX1262 regulator requested: %s", useRegulatorLDO ? "LDO" : "DC-DC"); + #if defined(P_LORA_SCLK) #ifdef NRF52_PLATFORM if (spi) { spi->setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); spi->begin(); } @@ -42,11 +51,12 @@ class CustomSX1262 : public SX1262 { if (spi) spi->begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); #endif #endif - int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo, useRegulatorLDO); // if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) { + MESH_DEBUG_PRINTLN("SX1262 init failed with error %d, retrying with TCXO at 0.0V", status); tcxo = 0.0f; - status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo, useRegulatorLDO); } if (status != RADIOLIB_ERR_NONE) { Serial.print("ERROR: radio init failed: "); @@ -83,6 +93,8 @@ class CustomSX1262 : public SX1262 { writeRegister(0x8B5, &r_data, 1); #endif + MESH_DEBUG_PRINTLN("SX1262 status=0x%02X device_errors=0x%04X", getStatus(), getDeviceErrors()); + return true; // success } From 4f7c1cb88f25578633ebd7bc5755f71d888c168b Mon Sep 17 00:00:00 2001 From: Christoph Koehler Date: Wed, 17 Jun 2026 08:55:54 -0600 Subject: [PATCH 29/57] build: add gcc, gtest to nix shell, fix native test framework --- default.nix | 7 ++++--- platformio.ini | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/default.nix b/default.nix index 4f9e1c59..13d410de 100644 --- a/default.nix +++ b/default.nix @@ -1,11 +1,12 @@ -{ pkgs ? import {} }: -let +{pkgs ? import {}}: let in pkgs.mkShell { buildInputs = [ pkgs.platformio pkgs.python3 + pkgs.gcc + pkgs.gtest # optional: needed as a programmer i.e. for esp32 pkgs.avrdude ]; -} + } diff --git a/platformio.ini b/platformio.ini index e16f7b83..b08854e5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -157,6 +157,7 @@ lib_deps = [env:native] platform = native +test_framework = googletest build_flags = -std=c++17 -I src -I test/mocks From 4f701b7aec539eee31d98aadfea79063866e53e8 Mon Sep 17 00:00:00 2001 From: Christoph Koehler Date: Wed, 1 Jul 2026 20:01:58 -0600 Subject: [PATCH 30/57] refactor: split MeshTables::hasSeen into pure query + markSeen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hasSeen() was simultaneously a predicate and a mutator — it inserted the packet hash on every miss, making five call sites that only wanted to mark a packet as sent call it with the return value discarded. Split into: - wasSeen() — pure predicate, no side effects - markSeen() — explicit insert All query sites now call markSeen() immediately after wasSeen() returns false, preserving identical runtime behaviour. The five mark-only send sites (sendFlood, sendDirect, sendZeroHop x2) now call markSeen directly. Also fixes three bridge sites (BridgeBase, ESPNowBridge, RS232Bridge) that had the same query+implicit-insert pattern. Tests: add test/test_mesh_tables/ covering wasSeen purity, markSeen, dup stats, and clear. Update SHA256 mock to produce deterministic output (previously finalize() was a no-op). Add Packet.cpp to native build filter. --- platformio.ini | 1 + src/Mesh.cpp | 43 +++++--- src/Mesh.h | 5 +- src/helpers/SimpleMeshTables.h | 16 ++- src/helpers/bridges/BridgeBase.cpp | 3 +- src/helpers/bridges/BridgeBase.h | 2 +- src/helpers/bridges/ESPNowBridge.cpp | 3 +- src/helpers/bridges/RS232Bridge.cpp | 3 +- test/mocks/SHA256.h | 29 ++++- .../test_simple_mesh_tables.cpp | 103 ++++++++++++++++++ 10 files changed, 176 insertions(+), 32 deletions(-) create mode 100644 test/test_mesh_tables/test_simple_mesh_tables.cpp diff --git a/platformio.ini b/platformio.ini index b08854e5..47cc0ab8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -165,5 +165,6 @@ test_build_src = yes build_src_filter = -<*> +<../src/Utils.cpp> + +<../src/Packet.cpp> lib_deps = google/googletest @ 1.17.0 diff --git a/src/Mesh.cpp b/src/Mesh.cpp index e9b92262..c11f37ca 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -55,7 +55,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint16_t offset = (uint16_t)pkt->path_len << path_sz; if (offset >= len) { // TRACE has reached end of given path onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len); - } else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { + } else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->wasSeen(pkt)) { + _tables->markSeen(pkt); // append SNR (Not hash!) pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4); @@ -89,14 +90,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { return forwardMultipartDirect(pkt); } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { - if (!_tables->hasSeen(pkt)) { // don't retransmit! + if (!_tables->wasSeen(pkt)) { // don't retransmit! + _tables->markSeen(pkt); removeSelfFromPath(pkt); routeDirectRecvAcks(pkt, 0); } return ACTION_RELEASE; } - if (!_tables->hasSeen(pkt)) { + if (!_tables->wasSeen(pkt)) { + _tables->markSeen(pkt); removeSelfFromPath(pkt); uint32_t d = getDirectRetransmitDelay(pkt); @@ -117,7 +120,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { memcpy(&ack_crc, &pkt->payload[i], 4); i += 4; if (i > pkt->payload_len) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete ACK packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { + } else if (!_tables->wasSeen(pkt)) { + _tables->markSeen(pkt); onAckRecv(pkt, ack_crc); action = routeRecvPacket(pkt); } @@ -134,7 +138,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data if (i + CIPHER_MAC_SIZE >= pkt->payload_len) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { + } else if (!_tables->wasSeen(pkt)) { + _tables->markSeen(pkt); // NOTE: this is a 'first packet wins' impl. When receiving from multiple paths, the first to arrive wins. // For flood mode, the path may not be the 'best' in terms of hops. // FUTURE: could send back multiple paths, using createPathReturn(), and let sender choose which to use(?) @@ -197,7 +202,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data if (i + 2 >= pkt->payload_len) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { + } else if (!_tables->wasSeen(pkt)) { + _tables->markSeen(pkt); if (self_id.isHashMatch(&dest_hash)) { Identity sender(sender_pub_key); @@ -224,7 +230,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data if (i + 2 >= pkt->payload_len) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { + } else if (!_tables->wasSeen(pkt)) { + _tables->markSeen(pkt); // scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM) GroupChannel channels[4]; int num = searchChannelsByHash(&channel_hash, channels, 4); @@ -255,7 +262,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete advertisement packet", getLogDateTime()); } else if (self_id.matches(id.pub_key)) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): receiving SELF advert packet", getLogDateTime()); - } else if (!_tables->hasSeen(pkt)) { + } else if (!_tables->wasSeen(pkt)) { + _tables->markSeen(pkt); uint8_t* app_data = &pkt->payload[i]; int app_data_len = pkt->payload_len - i; if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; } @@ -282,7 +290,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { break; } case PAYLOAD_TYPE_RAW_CUSTOM: { - if (pkt->isRouteDirect() && !_tables->hasSeen(pkt)) { + if (pkt->isRouteDirect() && !_tables->wasSeen(pkt)) { + _tables->markSeen(pkt); onRawDataRecv(pkt); //action = routeRecvPacket(pkt); don't flood route these (yet) } @@ -300,7 +309,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { tmp.payload_len = pkt->payload_len - 1; memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); - if (!_tables->hasSeen(&tmp)) { + if (!_tables->wasSeen(&tmp)) { + _tables->markSeen(&tmp); uint32_t ack_crc; memcpy(&ack_crc, tmp.payload, 4); @@ -357,7 +367,8 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) { tmp.payload_len = pkt->payload_len - 1; memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); - if (!_tables->hasSeen(&tmp)) { // don't retransmit! + 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) } @@ -637,7 +648,7 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_si packet->header |= ROUTE_TYPE_FLOOD; packet->setPathHashSizeAndCount(path_hash_size, 0); - _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + _tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us uint8_t pri; if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { @@ -666,7 +677,7 @@ void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_m packet->transport_codes[1] = transport_codes[1]; packet->setPathHashSizeAndCount(path_hash_size, 0); - _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + _tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us uint8_t pri; if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { @@ -699,7 +710,7 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin pri = 0; } } - _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + _tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us sendPacket(packet, pri, delay_millis); } @@ -709,7 +720,7 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) { packet->path_len = 0; // path_len of zero means Zero Hop - _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + _tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us sendPacket(packet, 0, delay_millis); } @@ -722,7 +733,7 @@ void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay packet->path_len = 0; // path_len of zero means Zero Hop - _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + _tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us sendPacket(packet, 0, delay_millis); } diff --git a/src/Mesh.h b/src/Mesh.h index 932541db..49a299a6 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -15,8 +15,9 @@ public: */ class MeshTables { public: - virtual bool hasSeen(const Packet* packet) = 0; - virtual void clear(const Packet* packet) = 0; // remove this packet hash from table + virtual bool wasSeen(const Packet* packet) = 0; + virtual void markSeen(const Packet* packet) = 0; + virtual void clear(const Packet* packet) = 0; // remove this packet hash from table }; /** diff --git a/src/helpers/SimpleMeshTables.h b/src/helpers/SimpleMeshTables.h index 0b79cfb4..956f36fa 100644 --- a/src/helpers/SimpleMeshTables.h +++ b/src/helpers/SimpleMeshTables.h @@ -31,27 +31,31 @@ public: } #endif - bool hasSeen(const mesh::Packet* packet) override { + bool wasSeen(const mesh::Packet* packet) override { uint8_t hash[MAX_HASH_SIZE]; packet->calculatePacketHash(hash); const uint8_t* sp = _hashes; for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) { - if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) { + if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) { if (packet->isRouteDirect()) { - _direct_dups++; // keep some stats + _direct_dups++; } else { _flood_dups++; } return true; } } - - memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE); - _next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table return false; } + void markSeen(const mesh::Packet* packet) override { + uint8_t hash[MAX_HASH_SIZE]; + packet->calculatePacketHash(hash); + memcpy(&_hashes[_next_idx * MAX_HASH_SIZE], hash, MAX_HASH_SIZE); + _next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; + } + void clear(const mesh::Packet* packet) override { uint8_t hash[MAX_HASH_SIZE]; packet->calculatePacketHash(hash); diff --git a/src/helpers/bridges/BridgeBase.cpp b/src/helpers/bridges/BridgeBase.cpp index d2e2e5e0..8093d3cb 100644 --- a/src/helpers/bridges/BridgeBase.cpp +++ b/src/helpers/bridges/BridgeBase.cpp @@ -39,7 +39,8 @@ void BridgeBase::handleReceivedPacket(mesh::Packet *packet) { return; } - if (!_seen_packets.hasSeen(packet)) { + if (!_seen_packets.wasSeen(packet)) { + _seen_packets.markSeen(packet); // bridge_delay provides a buffer to prevent immediate processing conflicts in the mesh network. _mgr->queueInbound(packet, millis() + _prefs->bridge_delay); } else { diff --git a/src/helpers/bridges/BridgeBase.h b/src/helpers/bridges/BridgeBase.h index 04c1564b..8bbe6466 100644 --- a/src/helpers/bridges/BridgeBase.h +++ b/src/helpers/bridges/BridgeBase.h @@ -110,7 +110,7 @@ protected: * @brief Common packet handling for received packets * * Implements the standard pattern used by all bridges: - * - Check if packet was seen before using _seen_packets.hasSeen() + * - Check if packet was seen before using _seen_packets.wasSeen() * - Queue packet for mesh processing if not seen before * - Free packet if already seen to prevent duplicates * diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index 808e9df4..3c094e7c 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -167,7 +167,8 @@ void ESPNowBridge::sendPacket(mesh::Packet *packet) { return; } - if (!_seen_packets.hasSeen(packet)) { + if (!_seen_packets.wasSeen(packet)) { + _seen_packets.markSeen(packet); // Create a temporary buffer just for size calculation and reuse for actual writing uint8_t sizingBuffer[MAX_PAYLOAD_SIZE]; uint16_t meshPacketLen = packet->writeTo(sizingBuffer); diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 0024f6f2..f719d342 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -115,7 +115,8 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) { return; } - if (!_seen_packets.hasSeen(packet)) { + if (!_seen_packets.wasSeen(packet)) { + _seen_packets.markSeen(packet); uint8_t buffer[MAX_SERIAL_PACKET_SIZE]; uint16_t len = packet->writeTo(buffer + 4); diff --git a/test/mocks/SHA256.h b/test/mocks/SHA256.h index b6e551a0..0cefe5c6 100644 --- a/test/mocks/SHA256.h +++ b/test/mocks/SHA256.h @@ -3,12 +3,33 @@ #include #include -// Mock SHA256 class for testing -// Provides minimal interface to allow Utils.cpp to compile +// Mock SHA256 for native testing — deterministic but not cryptographic. +// finalize() writes real (non-garbage) output so calculatePacketHash() produces +// distinguishable results for packets with different payloads. +#include + class SHA256 { + uint8_t _state[32]; + size_t _len; public: - void update(const uint8_t* data, size_t len) {} - void finalize(uint8_t* hash, size_t hashLen) {} + SHA256() : _len(0) { memset(_state, 0, sizeof(_state)); } + + void update(const void* data, size_t len) { + const uint8_t* bytes = static_cast(data); + for (size_t i = 0; i < len; i++) { + uint8_t b = bytes[i]; + _state[_len % 32] ^= b; + _state[(_len + 1) % 32] += (uint8_t)((b >> 1) | (b << 7)); + _len++; + } + } + + void finalize(uint8_t* hash, size_t hashLen) { + for (size_t i = 0; i < hashLen; i++) { + hash[i] = _state[i % 32]; + } + } + void resetHMAC(const uint8_t* key, size_t keyLen) {} void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {} }; diff --git a/test/test_mesh_tables/test_simple_mesh_tables.cpp b/test/test_mesh_tables/test_simple_mesh_tables.cpp new file mode 100644 index 00000000..46b477d9 --- /dev/null +++ b/test/test_mesh_tables/test_simple_mesh_tables.cpp @@ -0,0 +1,103 @@ +#include +#include "helpers/SimpleMeshTables.h" + +using namespace mesh; + +// Build a packet that calculatePacketHash() distinguishes by payload content. +// header selects ROUTE_TYPE_FLOOD so isRouteDirect() returns false. +static Packet makeFloodPacket(uint8_t seed) { + Packet p; + p.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT); + p.payload[0] = seed; + p.payload_len = 1; + p.path_len = 0; + return p; +} + +static Packet makeDirectPacket(uint8_t seed) { + Packet p; + p.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT); + p.payload[0] = seed; + p.payload_len = 1; + p.path_len = 0; + return p; +} + +// ── wasSeen: pure query ─────────────────────────────────────────────────────── + +TEST(SimpleMeshTables, WasSeen_ReturnsFalseForUnseen) { + SimpleMeshTables t; + Packet p = makeFloodPacket(0x01); + EXPECT_FALSE(t.wasSeen(&p)); +} + +// wasSeen shouldn't change state +TEST(SimpleMeshTables, WasSeen_IsPureQuery_DoesNotInsert) { + SimpleMeshTables t; + Packet p = makeFloodPacket(0x01); + EXPECT_FALSE(t.wasSeen(&p)); + EXPECT_FALSE(t.wasSeen(&p)); +} + +// ── markSeen + wasSeen ─────────────────────────────────────────────────────── + +TEST(SimpleMeshTables, MarkSeen_MakesWasSeenReturnTrue) { + SimpleMeshTables t; + Packet p = makeFloodPacket(0x01); + t.markSeen(&p); + EXPECT_TRUE(t.wasSeen(&p)); +} + +TEST(SimpleMeshTables, MarkSeen_DoesNotAffectOtherPackets) { + SimpleMeshTables t; + Packet p1 = makeFloodPacket(0x01); + Packet p2 = makeFloodPacket(0x02); + t.markSeen(&p1); + EXPECT_FALSE(t.wasSeen(&p2)); +} + +// Canonical pattern used at every onRecvPacket call site: +// if (!wasSeen(pkt)) { markSeen(pkt); process(pkt); } +TEST(SimpleMeshTables, QueryThenMark_WorksCorrectly) { + SimpleMeshTables t; + Packet p = makeFloodPacket(0x01); + EXPECT_FALSE(t.wasSeen(&p)); + t.markSeen(&p); + EXPECT_TRUE(t.wasSeen(&p)); +} + +// ── dup stats ──────────────────────────────────────────────────────────────── + +TEST(SimpleMeshTables, WasSeen_IncrementsFloodDupStat) { + SimpleMeshTables t; + Packet p = makeFloodPacket(0x01); + t.markSeen(&p); + t.wasSeen(&p); + EXPECT_EQ(1u, t.getNumFloodDups()); + EXPECT_EQ(0u, t.getNumDirectDups()); +} + +TEST(SimpleMeshTables, WasSeen_IncrementsDirectDupStat) { + SimpleMeshTables t; + Packet p = makeDirectPacket(0x01); + t.markSeen(&p); + t.wasSeen(&p); + EXPECT_EQ(0u, t.getNumFloodDups()); + EXPECT_EQ(1u, t.getNumDirectDups()); +} + +// ── clear ──────────────────────────────────────────────────────────────────── + +TEST(SimpleMeshTables, Clear_RemovesSeenPacket) { + SimpleMeshTables t; + Packet p = makeFloodPacket(0x01); + t.markSeen(&p); + ASSERT_TRUE(t.wasSeen(&p)); + t.clear(&p); + EXPECT_FALSE(t.wasSeen(&p)); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 0d7379520ff3fe92c2ad06cf8b39b3bdceb29b45 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 1 Jul 2026 15:25:34 +0100 Subject: [PATCH 31/57] Fix `recv_pkt_region` incorrect usage The `recv_pkt_region` is set when processing a flood packet in `filterRecvFloodPacket` but direct/non-flood packets would never pass through that function, so the pointer was not cleared for them. `sendFloodReply` would then later use it blindly, which meant that the response would either inherit the region from the last flood packet, or refer to a non-initialised pointer if no region floods had been received yet. --- examples/simple_repeater/MyMesh.cpp | 7 +++---- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_room_server/MyMesh.cpp | 7 +++---- examples/simple_room_server/MyMesh.h | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index ca4cfad2..b66e1952 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -549,8 +549,7 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { return getRNG()->nextInt(0, 5*t + 1); } -bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { - // just try to determine region for packet (apply later in allowPacketForward()) +mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) { if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD); } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { @@ -562,8 +561,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { } else { recv_pkt_region = NULL; } - // do normal processing - return false; + return Mesh::onRecvPacket(pkt); } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, @@ -867,6 +865,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc set_radio_at = revert_radio_at = 0; _logging = false; region_load_active = false; + recv_pkt_region = NULL; #if MAX_NEIGHBOURS memset(neighbours, 0, sizeof(neighbours)); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index fb091a4c..0b2e7491 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -166,7 +166,7 @@ protected: } #endif - bool filterRecvFloodPacket(mesh::Packet* pkt) override; + mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 12d0b0c3..36978e80 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -290,8 +290,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) { return true; } -bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { - // just try to determine region for packet (apply later in allowPacketForward()) +mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) { if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD); } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { @@ -303,8 +302,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { } else { recv_pkt_region = NULL; } - // do normal processing - return false; + return Mesh::onRecvPacket(pkt); } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, @@ -627,6 +625,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _logging = false; region_load_active = false; set_radio_at = revert_radio_at = 0; + recv_pkt_region = NULL; // defaults memset(&_prefs, 0, sizeof(_prefs)); diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index e9e53ec9..6bab9dc2 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -154,7 +154,7 @@ protected: return _prefs.multi_acks; } - bool filterRecvFloodPacket(mesh::Packet* pkt) override; + mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; bool allowPacketForward(const mesh::Packet* packet) override; void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; From b40968a0f08b93cf14104e6f8d7ef45338c913ab Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 5 Jul 2026 12:23:22 +1200 Subject: [PATCH 32/57] stop tracking vscode extensions.json --- .vscode/extensions.json | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 8057bc70..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "recommendations": [ - "pioarduino.pioarduino-ide", - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} From a92046b0eb73d5bc40af07c70c14fff246b196a7 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 5 Jul 2026 21:20:13 +0700 Subject: [PATCH 33/57] Put powerOff and enterDeepSleep to ESP32Board --- src/helpers/ESP32Board.cpp | 42 ++++++++++++++++++++++++++++++++++++++ src/helpers/ESP32Board.h | 3 +++ 2 files changed, 45 insertions(+) diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp index e0ca1d0e..a55abb26 100644 --- a/src/helpers/ESP32Board.cpp +++ b/src/helpers/ESP32Board.cpp @@ -1,6 +1,7 @@ #ifdef ESP_PLATFORM #include "ESP32Board.h" +#include #if defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA) // Repeater or Room Server only #include @@ -44,4 +45,45 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { } #endif +void ESP32Board::powerOff() { + enterDeepSleep(0); // Do not wakeup +} + +void ESP32Board::enterDeepSleep(uint32_t secs) { + // Power off the display if any +#ifdef DISPLAY_CLASS + display.turnOff(); +#endif + + // Power off LoRa + radio_driver.powerOff(); + + // Keep LoRa inactive during deepsleep + digitalWrite(P_LORA_NSS, HIGH); +#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6) + gpio_hold_en((gpio_num_t)P_LORA_NSS); +#else + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); +#endif + + // Power off GPS if any + if (sensors.getLocationProvider() != NULL) { + sensors.getLocationProvider()->stop(); + } + + // Flush serial buffers + Serial.flush(); + delay(100); + + // Clear stale wakeup sources to avoid ghost wakeup + // This is required when Power Management and automatic lightsleep are enabled + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000ULL); + } + + // Finally set ESP32 into deepsleep + esp_deep_sleep_start(); // CPU halts here and never returns! +} #endif diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 1efc99f3..45d7761b 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -62,6 +62,9 @@ public: return raw / 4; } + virtual void powerOff() override; + void enterDeepSleep(uint32_t secs); + uint32_t getIRQGpio() override { return P_LORA_DIO_1; // default for SX1262 } From 73b7367e4559d42f8b9ac8329a0c4d36ba0408d5 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 5 Jul 2026 21:20:39 +0700 Subject: [PATCH 34/57] Put powerOff to NRF52Board --- src/helpers/NRF52Board.cpp | 32 ++++++++++++++++++++++++++++++++ src/helpers/NRF52Board.h | 1 + 2 files changed, 33 insertions(+) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 17265f04..beee3212 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -1,5 +1,6 @@ #if defined(NRF52_PLATFORM) #include "NRF52Board.h" +#include #include #include @@ -297,6 +298,37 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } +void NRF52Board::powerOff() { + // Power off the display if any +#ifdef DISPLAY_CLASS + display.turnOff(); +#endif + + // Power off LoRa + radio_driver.powerOff(); + + // Keep LoRa inactive during deepsleep + digitalWrite(P_LORA_NSS, HIGH); + + // Power off GPS if any + if(sensors.getLocationProvider() != NULL) { + sensors.getLocationProvider()->stop(); + } + + // Flush serial buffers + Serial.flush(); + delay(100); + + // Enter SYSTEMOFF + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { // SoftDevice is enabled + sd_power_system_off(); + } else { // SoftDevice is not enable + NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter; + } +} + bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) { static const char BOOTLOADER_MARKER[] = "UF2 Bootloader "; const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 17065cf4..cbf4cd49 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -50,6 +50,7 @@ public: virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + virtual void powerOff() override; virtual bool getBootloaderVersion(char* version, size_t max_len) override; virtual bool startOTAUpdate(const char *id, char reply[]) override; virtual void sleep(uint32_t secs) override; From 7d7de8870719bce1178b9966f0c1c18ce18dba63 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 5 Jul 2026 21:22:13 +0700 Subject: [PATCH 35/57] board->powerOff() is sufficient to power off radio, display, GPS and components --- examples/companion_radio/ui-new/UITask.cpp | 3 +-- examples/companion_radio/ui-orig/UITask.cpp | 2 +- examples/companion_radio/ui-tiny/UITask.cpp | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c842019..403ea463 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -697,8 +697,7 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - _display->turnOff(); - radio_driver.powerOff(); + // Power off board including radio, display, GPS and components _board->powerOff(); } } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 55290467..b48f6412 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -307,7 +307,7 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - radio_driver.powerOff(); + // Power off board including radio, display, GPS and components _board->powerOff(); } } diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index 0119475e..452c02d4 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -566,8 +566,7 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - _display->turnOff(); - radio_driver.powerOff(); + // Power off board including radio, display, GPS and components _board->powerOff(); } } From eec75eab312d8338001760a3ac17cfd7518af5e8 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 5 Jul 2026 22:10:47 +0700 Subject: [PATCH 36/57] Cleanup Poweroff for NRF52 boards --- .../GAT56230SMeshKitBoard.h | 2 +- .../gat562_mesh_evb_pro/GAT562EVBProBoard.h | 2 +- .../GAT562MeshTrackerProBoard.h | 2 +- .../GAT56MeshWatch13Board.h | 2 +- variants/heltec_t096/T096Board.cpp | 10 ++----- variants/heltec_t1/T1Board.cpp | 30 +------------------ variants/heltec_t114/T114Board.h | 7 ++--- variants/keepteen_lt1/KeepteenLT1Board.h | 4 --- .../lilygo_t_impulse_plus/TImpulsePlusBoard.h | 6 ++-- variants/lilygo_techo/TechoBoard.h | 2 +- variants/lilygo_techo_card/TechoCardBoard.cpp | 2 +- variants/lilygo_techo_lite/TechoBoard.h | 3 +- variants/mesh_pocket/MeshPocket.h | 4 --- variants/meshtiny/MeshtinyBoard.h | 2 +- .../MinewsemiME25LS01Board.h | 2 +- variants/nano_g2_ultra/nano-g2.h | 2 +- variants/promicro/PromicroBoard.h | 4 --- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 2 +- variants/sensecap_solar/SenseCapSolarBoard.h | 2 +- variants/t1000-e/T1000eBoard.h | 2 +- variants/thinknode_m1/ThinkNodeM1Board.h | 3 +- variants/thinknode_m3/ThinkNodeM3Board.h | 2 +- variants/thinknode_m6/ThinkNodeM6Board.h | 2 +- variants/wio-tracker-l1/WioTrackerL1Board.h | 2 +- variants/xiao_nrf52/XiaoNrf52Board.h | 2 +- 25 files changed, 27 insertions(+), 76 deletions(-) diff --git a/variants/gat562_30s_mesh_kit/GAT56230SMeshKitBoard.h b/variants/gat562_30s_mesh_kit/GAT56230SMeshKitBoard.h index ab7ecc24..0b0f701c 100644 --- a/variants/gat562_30s_mesh_kit/GAT56230SMeshKitBoard.h +++ b/variants/gat562_30s_mesh_kit/GAT56230SMeshKitBoard.h @@ -47,7 +47,7 @@ public: uint32_t button_pin = PIN_BUTTON1; nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW); - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/gat562_mesh_evb_pro/GAT562EVBProBoard.h b/variants/gat562_mesh_evb_pro/GAT562EVBProBoard.h index 33c3d05f..66c77099 100644 --- a/variants/gat562_mesh_evb_pro/GAT562EVBProBoard.h +++ b/variants/gat562_mesh_evb_pro/GAT562EVBProBoard.h @@ -47,7 +47,7 @@ public: uint32_t button_pin = PIN_BUTTON1; nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW); - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/gat562_mesh_tracker_pro/GAT562MeshTrackerProBoard.h b/variants/gat562_mesh_tracker_pro/GAT562MeshTrackerProBoard.h index aa1772e4..39b55911 100644 --- a/variants/gat562_mesh_tracker_pro/GAT562MeshTrackerProBoard.h +++ b/variants/gat562_mesh_tracker_pro/GAT562MeshTrackerProBoard.h @@ -47,7 +47,7 @@ public: uint32_t button_pin = PIN_BUTTON1; nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW); - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/gat562_mesh_watch13/GAT56MeshWatch13Board.h b/variants/gat562_mesh_watch13/GAT56MeshWatch13Board.h index da792b78..805ca67f 100644 --- a/variants/gat562_mesh_watch13/GAT56MeshWatch13Board.h +++ b/variants/gat562_mesh_watch13/GAT56MeshWatch13Board.h @@ -38,7 +38,7 @@ public: uint32_t button_pin = PIN_BUTTON1; nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW); - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/heltec_t096/T096Board.cpp b/variants/heltec_t096/T096Board.cpp index 54425145..78af529d 100644 --- a/variants/heltec_t096/T096Board.cpp +++ b/variants/heltec_t096/T096Board.cpp @@ -112,13 +112,9 @@ void T096Board::variant_shutdown() { } void T096Board::powerOff() { -#if ENV_INCLUDE_GPS == 1 - pinMode(PIN_GPS_EN, OUTPUT); - digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE); -#endif - loRaFEMControl.setSleepModeEnable(); - variant_shutdown(); - sd_power_system_off(); + loRaFEMControl.setSleepModeEnable(); + nrf_gpio_cfg_default(PIN_GPS_EN); // 363uA down to 39uA + NRF52Board::powerOff(); } const char* T096Board::getManufacturerName() const { diff --git a/variants/heltec_t1/T1Board.cpp b/variants/heltec_t1/T1Board.cpp index 490f8678..1ef5b766 100644 --- a/variants/heltec_t1/T1Board.cpp +++ b/variants/heltec_t1/T1Board.cpp @@ -81,34 +81,6 @@ uint16_t T1Board::getBattMilliVolts() { } void T1Board::variant_shutdown() { - nrf_gpio_cfg_default(PIN_TFT_CS); - nrf_gpio_cfg_default(PIN_TFT_DC); - nrf_gpio_cfg_default(PIN_TFT_SDA); - nrf_gpio_cfg_default(PIN_TFT_SCL); - nrf_gpio_cfg_default(PIN_TFT_RST); - nrf_gpio_cfg_default(PIN_TFT_LEDA_CTL); - nrf_gpio_cfg_default(PIN_TFT_VDD_CTL); - - nrf_gpio_cfg_default(PIN_WIRE_SDA); - nrf_gpio_cfg_default(PIN_WIRE_SCL); - - nrf_gpio_cfg_default(LORA_CS); - nrf_gpio_cfg_default(SX126X_DIO1); - nrf_gpio_cfg_default(SX126X_BUSY); - nrf_gpio_cfg_default(SX126X_RESET); - nrf_gpio_cfg_default(PIN_SPI_MISO); - nrf_gpio_cfg_default(PIN_SPI_MOSI); - nrf_gpio_cfg_default(PIN_SPI_SCK); - - nrf_gpio_cfg_default(PIN_SPI1_MOSI); - nrf_gpio_cfg_default(PIN_SPI1_SCK); - - nrf_gpio_cfg_default(PIN_GPS_RESET); - nrf_gpio_cfg_default(PIN_GPS_EN); - nrf_gpio_cfg_default(PIN_GPS_PPS); - nrf_gpio_cfg_default(PIN_GPS_RX); - nrf_gpio_cfg_default(PIN_GPS_TX); - nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_1); nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_2); @@ -127,7 +99,7 @@ void T1Board::variant_shutdown() { void T1Board::powerOff() { variant_shutdown(); - sd_power_system_off(); + NRF52Board::powerOff(); } const char* T1Board::getManufacturerName() const { diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index f27dc291..bd76d3de 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -50,10 +50,7 @@ public: #ifdef LED_PIN digitalWrite(LED_PIN, HIGH); #endif -#if ENV_INCLUDE_GPS == 1 - pinMode(GPS_EN, OUTPUT); - digitalWrite(GPS_EN, LOW); -#endif - sd_power_system_off(); + + NRF52Board::powerOff(); } }; diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index 752b27e7..25d4d867 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -37,8 +37,4 @@ public: digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off } #endif - - void powerOff() override { - sd_power_system_off(); - } }; diff --git a/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h b/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h index ea1782cf..5fafcad8 100644 --- a/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h +++ b/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h @@ -48,12 +48,10 @@ public: } void powerOff() override { + // power off system + NRF52Board::powerOff(); // turn off 3.3v digitalWrite(RT9080_EN, LOW); - - // power off system - sd_power_system_off(); - } }; diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index e560cd14..e957d2e5 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -24,6 +24,7 @@ public: } void powerOff() override { + NRF52Board::powerOff(); #ifdef LED_RED digitalWrite(LED_RED, HIGH); #endif @@ -39,6 +40,5 @@ public: #ifdef PIN_PWR_EN digitalWrite(PIN_PWR_EN, LOW); #endif - sd_power_system_off(); } }; diff --git a/variants/lilygo_techo_card/TechoCardBoard.cpp b/variants/lilygo_techo_card/TechoCardBoard.cpp index f0dcef31..8143587d 100644 --- a/variants/lilygo_techo_card/TechoCardBoard.cpp +++ b/variants/lilygo_techo_card/TechoCardBoard.cpp @@ -91,7 +91,7 @@ void TechoCardBoard::powerOff() { nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); turnOffLeds(); digitalWrite(PIN_PWR_EN, LOW); - sd_power_system_off(); + NRF52Board::powerOff(); } #endif diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 7a43fd83..f4c16016 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -22,6 +22,8 @@ public: } void powerOff() override { + NRF52Board::powerOff(); + digitalWrite(PIN_VBAT_MEAS_EN, LOW); #ifdef LED_RED digitalWrite(LED_RED, LOW); @@ -38,6 +40,5 @@ public: #ifdef PIN_PWR_EN digitalWrite(PIN_PWR_EN, LOW); #endif - sd_power_system_off(); } }; \ No newline at end of file diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 478bd56d..e215eb77 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -32,8 +32,4 @@ public: const char* getManufacturerName() const override { return "Heltec MeshPocket"; } - - void powerOff() override { - sd_power_system_off(); - } }; diff --git a/variants/meshtiny/MeshtinyBoard.h b/variants/meshtiny/MeshtinyBoard.h index b69c0e41..67a521fe 100644 --- a/variants/meshtiny/MeshtinyBoard.h +++ b/variants/meshtiny/MeshtinyBoard.h @@ -60,7 +60,7 @@ public: nrf_gpio_cfg_sense_input(g_ADigitalPinMap[PIN_USER_BTN], NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); #endif - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 4fa5cd41..450a3d13 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -65,7 +65,7 @@ public: #ifdef BUTTON_PIN nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); #endif - sd_power_system_off(); + NRF52Board::powerOff(); } #if defined(P_LORA_TX_LED) diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index cf771efe..bf9543b0 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -52,6 +52,6 @@ public: nrf_gpio_cfg_sense_input(digitalPinToInterrupt(PIN_USER_BTN), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 7b6afb1b..b190c47c 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -72,8 +72,4 @@ public: #endif return 0; } - - void powerOff() override { - sd_power_system_off(); - } }; diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index cc5aa06f..daa90d74 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -69,6 +69,6 @@ public: nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); #endif - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index 6799a5e9..5a8861c2 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -54,7 +54,7 @@ public: #ifdef NRF52_POWER_MANAGEMENT initiateShutdown(SHUTDOWN_REASON_USER); #else - sd_power_system_off(); + NRF52Board::powerOff(); #endif } }; diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index e7653fb2..1111d3c4 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -88,6 +88,6 @@ public: nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH); #endif - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index ebc46e6e..94224d9c 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -40,7 +40,6 @@ public: #endif // power off board - sd_power_system_off(); - + NRF52Board::powerOff(); } }; diff --git a/variants/thinknode_m3/ThinkNodeM3Board.h b/variants/thinknode_m3/ThinkNodeM3Board.h index 1435d31d..396d80d1 100644 --- a/variants/thinknode_m3/ThinkNodeM3Board.h +++ b/variants/thinknode_m3/ThinkNodeM3Board.h @@ -49,6 +49,6 @@ public: #endif // power off board - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h index 32baa2a0..78815e2c 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.h +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -44,6 +44,6 @@ public: #endif // power off board - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index 052238e6..e376e066 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -35,6 +35,6 @@ public: } void powerOff() override { - sd_power_system_off(); + NRF52Board::powerOff(); } }; diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 2790dbad..b2638a44 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -50,7 +50,7 @@ public: nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); #endif - sd_power_system_off(); + NRF52Board::powerOff(); } }; From 6b205da4e9935266b23c30a794045e1ebe4688c2 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 5 Jul 2026 23:33:56 +0700 Subject: [PATCH 37/57] Added missing driver/rtc_io.h --- src/helpers/ESP32Board.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 45d7761b..d7eb5fee 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -14,6 +14,7 @@ #include #include "soc/rtc.h" #include "esp_system.h" +#include class ESP32Board : public mesh::MainBoard { protected: From 35f654ced36a39152606a2aa77d7c95c90e457cd Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 5 Jul 2026 23:47:27 +0700 Subject: [PATCH 38/57] Fixed hibernate/powerOff for ESP32 boards to stay at uA --- src/helpers/MeshadventurerBoard.h | 30 ---------- variants/heltec_e213/HeltecE213Board.cpp | 27 --------- variants/heltec_e213/HeltecE213Board.h | 3 - variants/heltec_e290/HeltecE290Board.cpp | 27 --------- variants/heltec_e290/HeltecE290Board.h | 3 - variants/heltec_t190/HeltecT190Board.cpp | 27 --------- variants/heltec_t190/HeltecT190Board.h | 3 - .../HeltecTrackerV2Board.cpp | 31 ++-------- .../heltec_tracker_v2/HeltecTrackerV2Board.h | 2 - variants/heltec_v2/HeltecV2Board.h | 25 -------- variants/heltec_v3/HeltecV3Board.h | 29 ---------- variants/heltec_v4/HeltecV4Board.cpp | 31 ++-------- variants/heltec_v4/HeltecV4Board.h | 2 - variants/lilygo_tdeck/TDeckBoard.h | 24 -------- variants/rak3112/RAK3112Board.h | 29 ---------- variants/station_g2/StationG2Board.h | 24 -------- variants/station_g3_esp32/StationG3Board.cpp | 15 +++++ variants/station_g3_esp32/StationG3Board.h | 31 +--------- variants/thinknode_m2/ThinknodeM2Board.cpp | 58 ++++++++----------- variants/thinknode_m2/ThinknodeM2Board.h | 4 -- variants/thinknode_m5/ThinknodeM5Board.cpp | 8 --- variants/thinknode_m5/ThinknodeM5Board.h | 3 - variants/xiao_c3/XiaoC3Board.h | 32 ---------- 23 files changed, 50 insertions(+), 418 deletions(-) create mode 100644 variants/station_g3_esp32/StationG3Board.cpp diff --git a/src/helpers/MeshadventurerBoard.h b/src/helpers/MeshadventurerBoard.h index 65e11102..0325161d 100644 --- a/src/helpers/MeshadventurerBoard.h +++ b/src/helpers/MeshadventurerBoard.h @@ -15,8 +15,6 @@ #include "ESP32Board.h" -#include - class MeshadventurerBoard : public ESP32Board { public: @@ -35,34 +33,6 @@ public: } } - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are held on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - - void powerOff() override { - // TODO: re-enable this when there is a definite wake-up source pin: - // enterDeepSleep(0); - } - uint16_t getBattMilliVolts() override { analogReadResolution(12); diff --git a/variants/heltec_e213/HeltecE213Board.cpp b/variants/heltec_e213/HeltecE213Board.cpp index af115318..88737c4d 100644 --- a/variants/heltec_e213/HeltecE213Board.cpp +++ b/variants/heltec_e213/HeltecE213Board.cpp @@ -20,33 +20,6 @@ void HeltecE213Board::begin() { } } - void HeltecE213Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - - void HeltecE213Board::powerOff() { - enterDeepSleep(0); - } - uint16_t HeltecE213Board::getBattMilliVolts() { analogReadResolution(10); digitalWrite(PIN_ADC_CTRL, HIGH); diff --git a/variants/heltec_e213/HeltecE213Board.h b/variants/heltec_e213/HeltecE213Board.h index 2192c141..fadc038f 100644 --- a/variants/heltec_e213/HeltecE213Board.h +++ b/variants/heltec_e213/HeltecE213Board.h @@ -3,7 +3,6 @@ #include #include #include -#include class HeltecE213Board : public ESP32Board { @@ -13,8 +12,6 @@ public: HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } void begin(); - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); - void powerOff() override; uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; }; diff --git a/variants/heltec_e290/HeltecE290Board.cpp b/variants/heltec_e290/HeltecE290Board.cpp index 3994a206..96ec59c9 100644 --- a/variants/heltec_e290/HeltecE290Board.cpp +++ b/variants/heltec_e290/HeltecE290Board.cpp @@ -20,33 +20,6 @@ void HeltecE290Board::begin() { } } - void HeltecE290Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - - void HeltecE290Board::powerOff() { - enterDeepSleep(0); - } - uint16_t HeltecE290Board::getBattMilliVolts() { analogReadResolution(10); digitalWrite(PIN_ADC_CTRL, HIGH); diff --git a/variants/heltec_e290/HeltecE290Board.h b/variants/heltec_e290/HeltecE290Board.h index 645ec348..f287227c 100644 --- a/variants/heltec_e290/HeltecE290Board.h +++ b/variants/heltec_e290/HeltecE290Board.h @@ -3,7 +3,6 @@ #include #include #include -#include class HeltecE290Board : public ESP32Board { @@ -13,8 +12,6 @@ public: HeltecE290Board() : periph_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { } void begin(); - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); - void powerOff() override; uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; diff --git a/variants/heltec_t190/HeltecT190Board.cpp b/variants/heltec_t190/HeltecT190Board.cpp index 4f35be40..0a16b52b 100644 --- a/variants/heltec_t190/HeltecT190Board.cpp +++ b/variants/heltec_t190/HeltecT190Board.cpp @@ -20,33 +20,6 @@ void HeltecT190Board::begin() { } } - void HeltecT190Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - - void HeltecT190Board::powerOff() { - enterDeepSleep(0); - } - uint16_t HeltecT190Board::getBattMilliVolts() { analogReadResolution(10); digitalWrite(PIN_ADC_CTRL, HIGH); diff --git a/variants/heltec_t190/HeltecT190Board.h b/variants/heltec_t190/HeltecT190Board.h index bc38c1e0..557c070e 100644 --- a/variants/heltec_t190/HeltecT190Board.h +++ b/variants/heltec_t190/HeltecT190Board.h @@ -3,7 +3,6 @@ #include #include #include -#include class HeltecT190Board : public ESP32Board { @@ -13,8 +12,6 @@ public: HeltecT190Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } void begin(); - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); - void powerOff() override; uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index f182c905..99b1cdfe 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -35,33 +35,12 @@ void HeltecTrackerV2Board::begin() { loRaFEMControl.setRxModeEnable(); } - void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + void HeltecTrackerV2Board::powerOff() { + // Turn off PA + digitalWrite(P_LORA_PA_POWER, LOW); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - loRaFEMControl.setRxModeEnableWhenMCUSleep();//It also needs to be enabled in receive mode - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - - void HeltecTrackerV2Board::powerOff() { - enterDeepSleep(0); + ESP32Board::powerOff(); } uint16_t HeltecTrackerV2Board::getBattMilliVolts() { diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.h b/variants/heltec_tracker_v2/HeltecTrackerV2Board.h index ccbecc7a..2bd6a025 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.h +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.h @@ -3,7 +3,6 @@ #include #include #include -#include #include "LoRaFEMControl.h" class HeltecTrackerV2Board : public ESP32Board { @@ -17,7 +16,6 @@ public: void begin(); void onBeforeTransmit(void) override; void onAfterTransmit(void) override; - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); void powerOff() override; uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; diff --git a/variants/heltec_v2/HeltecV2Board.h b/variants/heltec_v2/HeltecV2Board.h index fe800890..9b08fe94 100644 --- a/variants/heltec_v2/HeltecV2Board.h +++ b/variants/heltec_v2/HeltecV2Board.h @@ -7,8 +7,6 @@ #define PIN_VBAT_READ 37 #define PIN_LED_BUILTIN 25 -#include - class HeltecV2Board : public ESP32Board { public: void begin() { @@ -26,29 +24,6 @@ public: } } - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_0, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_0); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - uint16_t getBattMilliVolts() override { analogReadResolution(10); diff --git a/variants/heltec_v3/HeltecV3Board.h b/variants/heltec_v3/HeltecV3Board.h index ba22a7f2..7e7abe31 100644 --- a/variants/heltec_v3/HeltecV3Board.h +++ b/variants/heltec_v3/HeltecV3Board.h @@ -17,8 +17,6 @@ #define PIN_ADC_CTRL_ACTIVE LOW #define PIN_ADC_CTRL_INACTIVE HIGH -#include - class HeltecV3Board : public ESP32Board { private: bool adc_active_state; @@ -52,33 +50,6 @@ public: } } - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - - void powerOff() override { - enterDeepSleep(0); - } - uint16_t getBattMilliVolts() override { analogReadResolution(10); digitalWrite(PIN_ADC_CTRL, adc_active_state); diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 3978c51e..3f13f41f 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -32,33 +32,12 @@ void HeltecV4Board::begin() { loRaFEMControl.setRxModeEnable(); } - void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + void HeltecV4Board::powerOff() { + // Turn off PA + digitalWrite(P_LORA_PA_POWER, LOW); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - loRaFEMControl.setRxModeEnableWhenMCUSleep();//It also needs to be enabled in receive mode - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - - void HeltecV4Board::powerOff() { - enterDeepSleep(0); + ESP32Board::powerOff(); } uint16_t HeltecV4Board::getBattMilliVolts() { diff --git a/variants/heltec_v4/HeltecV4Board.h b/variants/heltec_v4/HeltecV4Board.h index fc37b9f6..55166bb3 100644 --- a/variants/heltec_v4/HeltecV4Board.h +++ b/variants/heltec_v4/HeltecV4Board.h @@ -3,7 +3,6 @@ #include #include #include -#include #include "LoRaFEMControl.h" #ifndef ADC_MULTIPLIER @@ -23,7 +22,6 @@ public: void begin(); void onBeforeTransmit(void) override; void onAfterTransmit(void) override; - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); void powerOff() override; bool setLoRaFemLnaEnabled(bool enable) override; bool canControlLoRaFemLna() const override; diff --git a/variants/lilygo_tdeck/TDeckBoard.h b/variants/lilygo_tdeck/TDeckBoard.h index 7ed007af..e2844360 100644 --- a/variants/lilygo_tdeck/TDeckBoard.h +++ b/variants/lilygo_tdeck/TDeckBoard.h @@ -3,7 +3,6 @@ #include #include #include "helpers/ESP32Board.h" -#include #define PIN_VBAT_READ 4 #define BATTERY_SAMPLES 8 @@ -23,29 +22,6 @@ public: } #endif - void enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - uint16_t getBattMilliVolts() { #if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER) analogReadResolution(12); diff --git a/variants/rak3112/RAK3112Board.h b/variants/rak3112/RAK3112Board.h index 8ba3197c..704162b8 100644 --- a/variants/rak3112/RAK3112Board.h +++ b/variants/rak3112/RAK3112Board.h @@ -16,8 +16,6 @@ #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) #define BATTERY_SAMPLES 8 -#include - class RAK3112Board : public ESP32Board { private: bool adc_active_state; @@ -51,33 +49,6 @@ public: } } - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - - void powerOff() override { - enterDeepSleep(0); - } - uint16_t getBattMilliVolts() override { analogReadResolution(12); diff --git a/variants/station_g2/StationG2Board.h b/variants/station_g2/StationG2Board.h index a905682c..d1989ee0 100644 --- a/variants/station_g2/StationG2Board.h +++ b/variants/station_g2/StationG2Board.h @@ -2,7 +2,6 @@ #include #include -#include class StationG2Board : public ESP32Board { public: @@ -21,29 +20,6 @@ public: } } - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - uint16_t getBattMilliVolts() override { return 0; } diff --git a/variants/station_g3_esp32/StationG3Board.cpp b/variants/station_g3_esp32/StationG3Board.cpp new file mode 100644 index 00000000..4a498311 --- /dev/null +++ b/variants/station_g3_esp32/StationG3Board.cpp @@ -0,0 +1,15 @@ +#include "StationG3Board.h" + +void StationG3Board::powerOff() { +#ifdef P_PA1_EN + setPAModeHigh(false); + rtc_gpio_hold_en((gpio_num_t)P_PA1_EN); +#endif + +#ifdef P_PRIMARY_LNA_EN + setPrimaryLNAControl(true); + rtc_gpio_hold_en((gpio_num_t)P_PRIMARY_LNA_EN); +#endif + + ESP32Board::powerOff(); +} diff --git a/variants/station_g3_esp32/StationG3Board.h b/variants/station_g3_esp32/StationG3Board.h index dc440d95..4b1fb81c 100644 --- a/variants/station_g3_esp32/StationG3Board.h +++ b/variants/station_g3_esp32/StationG3Board.h @@ -73,36 +73,7 @@ public: setPrimaryLNAControl(true); } - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); - rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); - - rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - -#ifdef P_PA1_EN - setPAModeHigh(false); - rtc_gpio_hold_en((gpio_num_t)P_PA1_EN); -#endif - -#ifdef P_PRIMARY_LNA_EN - setPrimaryLNAControl(true); - rtc_gpio_hold_en((gpio_num_t)P_PRIMARY_LNA_EN); -#endif - - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); - } else { - esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1) | (1ULL << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - esp_deep_sleep_start(); - } + void powerOff() override; uint16_t getBattMilliVolts() override { return 0; diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp index 05965103..8d68006d 100644 --- a/variants/thinknode_m2/ThinknodeM2Board.cpp +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -1,40 +1,30 @@ #include "ThinknodeM2Board.h" - - void ThinknodeM2Board::begin() { - pinMode(PIN_VEXT_EN, OUTPUT); - digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle - delay(20); // allow power rail to discharge - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on - delay(120); // give display time to bias on cold boot - ESP32Board::begin(); - pinMode(PIN_STATUS_LED, OUTPUT); // init power led - } - - void ThinknodeM2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_deep_sleep_start(); - } - - void ThinknodeM2Board::powerOff() { - enterDeepSleep(0); - } - - uint16_t ThinknodeM2Board::getBattMilliVolts() { - analogReadResolution(12); - analogSetPinAttenuation(PIN_VBAT_READ, ADC_11db); - - uint32_t mv = 0; - for (int i = 0; i < 8; ++i) { - mv += analogReadMilliVolts(PIN_VBAT_READ); - delayMicroseconds(200); - } - mv /= 8; - - analogReadResolution(10); - return static_cast(mv * ADC_MULTIPLIER ); + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle + delay(20); // allow power rail to discharge + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on + delay(120); // give display time to bias on cold boot + ESP32Board::begin(); + pinMode(PIN_STATUS_LED, OUTPUT); // init power led } - const char* ThinknodeM2Board::getManufacturerName() const { - return "Elecrow ThinkNode M2"; +uint16_t ThinknodeM2Board::getBattMilliVolts() { + analogReadResolution(12); + analogSetPinAttenuation(PIN_VBAT_READ, ADC_11db); + + uint32_t mv = 0; + for (int i = 0; i < 8; ++i) { + mv += analogReadMilliVolts(PIN_VBAT_READ); + delayMicroseconds(200); } + mv /= 8; + + analogReadResolution(10); + return static_cast(mv * ADC_MULTIPLIER); +} + +const char *ThinknodeM2Board::getManufacturerName() const { + return "Elecrow ThinkNode M2"; +} diff --git a/variants/thinknode_m2/ThinknodeM2Board.h b/variants/thinknode_m2/ThinknodeM2Board.h index 8011fae6..02556777 100644 --- a/variants/thinknode_m2/ThinknodeM2Board.h +++ b/variants/thinknode_m2/ThinknodeM2Board.h @@ -3,15 +3,11 @@ #include #include #include -#include class ThinknodeM2Board : public ESP32Board { public: - void begin(); - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); - void powerOff() override; uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index c4de538c..2cb138e6 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -19,14 +19,6 @@ void ThinknodeM5Board::begin() { ESP32Board::begin(); } - void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { - esp_deep_sleep_start(); - } - - void ThinknodeM5Board::powerOff() { - enterDeepSleep(0); - } - uint16_t ThinknodeM5Board::getBattMilliVolts() { analogReadResolution(12); analogSetPinAttenuation(PIN_VBAT_READ, ADC_11db); diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h index 3c120027..57ff0d00 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.h +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -3,7 +3,6 @@ #include #include #include -#include #include extern PCA9557 expander; @@ -13,8 +12,6 @@ class ThinknodeM5Board : public ESP32Board { public: void begin(); - void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); - void powerOff() override; uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; diff --git a/variants/xiao_c3/XiaoC3Board.h b/variants/xiao_c3/XiaoC3Board.h index 6ea1c15f..c5700c68 100644 --- a/variants/xiao_c3/XiaoC3Board.h +++ b/variants/xiao_c3/XiaoC3Board.h @@ -3,7 +3,6 @@ #include #include -#include #include class XiaoC3Board : public ESP32Board { @@ -40,37 +39,6 @@ public: #endif } - void enterDeepSleep(uint32_t secs, int8_t wake_pin = -1) { - gpio_set_direction(gpio_num_t(P_LORA_DIO_1), GPIO_MODE_INPUT); - if (wake_pin >= 0) { - gpio_set_direction((gpio_num_t)wake_pin, GPIO_MODE_INPUT); - } - - //hold disable, isolate and power domain config functions may be unnecessary - //gpio_deep_sleep_hold_dis(); - //esp_sleep_config_gpio_isolate(); - gpio_deep_sleep_hold_en(); - -#if defined(LORA_TX_BOOST_PIN) - gpio_hold_en((gpio_num_t) LORA_TX_BOOST_PIN); - gpio_deep_sleep_hold_en(); -#endif - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - - if (wake_pin >= 0) { - esp_deep_sleep_enable_gpio_wakeup((1 << P_LORA_DIO_1) | (1 << wake_pin), ESP_GPIO_WAKEUP_GPIO_HIGH); - } else { - esp_deep_sleep_enable_gpio_wakeup(1 << P_LORA_DIO_1, ESP_GPIO_WAKEUP_GPIO_HIGH); - } - - if (secs > 0) { - esp_sleep_enable_timer_wakeup(secs * 1000000); - } - - // Finally set ESP32 into sleep - esp_deep_sleep_start(); // CPU halts here and never returns! - } - #if defined(LORA_TX_BOOST_PIN) || defined(P_LORA_TX_LED) void onBeforeTransmit() override { #if defined(P_LORA_TX_LED) From 584f9e909be71610ffe3b789f33ca02d10ea11f9 Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:52:18 +1000 Subject: [PATCH 39/57] Lilygo T-Echo Lite - Change SX1262 to use LDO instead of DC-DC (DCC pin on SX1262 not wired in, according to schematic) --- variants/lilygo_techo_lite/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index 4b5edf69..a9b3d124 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -15,6 +15,7 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_USE_REGULATOR_LDO=1 -D P_LORA_TX_LED=LED_GREEN -D DISABLE_DIAGNOSTIC_OUTPUT -D ENV_INCLUDE_GPS=1 From cea246d0eba80af7d7a4447d7ad6f972222f83b7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 6 Jul 2026 15:09:18 +1000 Subject: [PATCH 40/57] * super fast ST7735 display driver --- src/helpers/ui/ST7735Display.cpp | 553 +++++++++++++++++++++++++--- src/helpers/ui/ST7735Display.h | 9 +- variants/heltec_t096/platformio.ini | 2 +- 3 files changed, 502 insertions(+), 62 deletions(-) diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index a6087dd8..2b9f3ebb 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -1,12 +1,404 @@ #include "ST7735Display.h" -#ifndef DISPLAY_ROTATION - #define DISPLAY_ROTATION 2 -#endif +//#include + +// Optimised ST7735 display driver, derived from Adafruit_ST7735 library. + +#define ST_CMD_DELAY 0x80 // special signifier for command lists + +#define ST77XX_NOP 0x00 +#define ST77XX_SWRESET 0x01 +#define ST77XX_RDDID 0x04 +#define ST77XX_RDDST 0x09 + +#define ST77XX_SLPIN 0x10 +#define ST77XX_SLPOUT 0x11 +#define ST77XX_PTLON 0x12 +#define ST77XX_NORON 0x13 + +#define ST77XX_INVOFF 0x20 +#define ST77XX_INVON 0x21 +#define ST77XX_DISPOFF 0x28 +#define ST77XX_DISPON 0x29 +#define ST77XX_CASET 0x2A +#define ST77XX_RASET 0x2B +#define ST77XX_RAMWR 0x2C +#define ST77XX_RAMRD 0x2E + +#define ST77XX_PTLAR 0x30 +#define ST77XX_TEOFF 0x34 +#define ST77XX_TEON 0x35 +#define ST77XX_MADCTL 0x36 +#define ST77XX_COLMOD 0x3A + +#define ST77XX_MADCTL_MY 0x80 +#define ST77XX_MADCTL_MX 0x40 +#define ST77XX_MADCTL_MV 0x20 +#define ST77XX_MADCTL_ML 0x10 +#define ST77XX_MADCTL_RGB 0x00 + +#define ST77XX_RDID1 0xDA +#define ST77XX_RDID2 0xDB +#define ST77XX_RDID3 0xDC +#define ST77XX_RDID4 0xDD + +// Some ready-made 16-bit ('565') color settings: +#define ST77XX_BLACK 0x0000 +#define ST77XX_WHITE 0xFFFF +#define ST77XX_RED 0xF800 +#define ST77XX_GREEN 0x07E0 +#define ST77XX_BLUE 0x001F +#define ST77XX_CYAN 0x07FF +#define ST77XX_MAGENTA 0xF81F +#define ST77XX_YELLOW 0xFFE0 +#define ST77XX_ORANGE 0xFC00 + + +// some flags for initR() :( +#define INITR_GREENTAB 0x00 +#define INITR_REDTAB 0x01 +#define INITR_BLACKTAB 0x02 +#define INITR_18GREENTAB INITR_GREENTAB +#define INITR_18REDTAB INITR_REDTAB +#define INITR_18BLACKTAB INITR_BLACKTAB +#define INITR_144GREENTAB 0x01 +#define INITR_MINI160x80 0x04 +#define INITR_HALLOWING 0x05 +#define INITR_MINI160x80_PLUGIN 0x06 + +// Some register settings +#define ST7735_MADCTL_BGR 0x08 +#define ST7735_MADCTL_MH 0x04 + +#define ST7735_FRMCTR1 0xB1 +#define ST7735_FRMCTR2 0xB2 +#define ST7735_FRMCTR3 0xB3 +#define ST7735_INVCTR 0xB4 +#define ST7735_DISSET5 0xB6 + +#define ST7735_PWCTR1 0xC0 +#define ST7735_PWCTR2 0xC1 +#define ST7735_PWCTR3 0xC2 +#define ST7735_PWCTR4 0xC3 +#define ST7735_PWCTR5 0xC4 +#define ST7735_VMCTR1 0xC5 + +#define ST7735_PWCTR6 0xFC + +#define ST7735_GMCTRP1 0xE0 +#define ST7735_GMCTRN1 0xE1 + +// Some ready-made 16-bit ('565') color settings: +#define ST7735_BLACK ST77XX_BLACK +#define ST7735_WHITE ST77XX_WHITE +#define ST7735_RED ST77XX_RED +#define ST7735_GREEN ST77XX_GREEN +#define ST7735_BLUE ST77XX_BLUE +#define ST7735_CYAN ST77XX_CYAN +#define ST7735_MAGENTA ST77XX_MAGENTA +#define ST7735_YELLOW ST77XX_YELLOW +#define ST7735_ORANGE ST77XX_ORANGE + +static TFT_eSPI lcd = TFT_eSPI(160, 80); +static uint32_t curr_color; + +#define _spi (&SPI1) + +SPISettings _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); + +// clang-format off +static const uint8_t PROGMEM + Bcmd[] = { // Init commands for 7735B screens + 18, // 18 commands in list: + ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, no args, w/delay + 50, // 50 ms delay + ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, no args, w/delay + 255, // 255 = max (500 ms) delay + ST77XX_COLMOD, 1+ST_CMD_DELAY, // 3: Set color mode, 1 arg + delay: + 0x05, // 16-bit color + 10, // 10 ms delay + ST7735_FRMCTR1, 3+ST_CMD_DELAY, // 4: Frame rate control, 3 args + delay: + 0x00, // fastest refresh + 0x06, // 6 lines front porch + 0x03, // 3 lines back porch + 10, // 10 ms delay + ST77XX_MADCTL, 1, // 5: Mem access ctl (directions), 1 arg: + 0x08, // Row/col addr, bottom-top refresh + ST7735_DISSET5, 2, // 6: Display settings #5, 2 args: + 0x15, // 1 clk cycle nonoverlap, 2 cycle gate + // rise, 3 cycle osc equalize + 0x02, // Fix on VTL + ST7735_INVCTR, 1, // 7: Display inversion control, 1 arg: + 0x0, // Line inversion + ST7735_PWCTR1, 2+ST_CMD_DELAY, // 8: Power control, 2 args + delay: + 0x02, // GVDD = 4.7V + 0x70, // 1.0uA + 10, // 10 ms delay + ST7735_PWCTR2, 1, // 9: Power control, 1 arg, no delay: + 0x05, // VGH = 14.7V, VGL = -7.35V + ST7735_PWCTR3, 2, // 10: Power control, 2 args, no delay: + 0x01, // Opamp current small + 0x02, // Boost frequency + ST7735_VMCTR1, 2+ST_CMD_DELAY, // 11: Power control, 2 args + delay: + 0x3C, // VCOMH = 4V + 0x38, // VCOML = -1.1V + 10, // 10 ms delay + ST7735_PWCTR6, 2, // 12: Power control, 2 args, no delay: + 0x11, 0x15, + ST7735_GMCTRP1,16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay: + 0x09, 0x16, 0x09, 0x20, // (Not entirely necessary, but provides + 0x21, 0x1B, 0x13, 0x19, // accurate colors) + 0x17, 0x15, 0x1E, 0x2B, + 0x04, 0x05, 0x02, 0x0E, + ST7735_GMCTRN1,16+ST_CMD_DELAY, // 14: Gamma Adjustments (neg. polarity), 16 args + delay: + 0x0B, 0x14, 0x08, 0x1E, // (Not entirely necessary, but provides + 0x22, 0x1D, 0x18, 0x1E, // accurate colors) + 0x1B, 0x1A, 0x24, 0x2B, + 0x06, 0x06, 0x02, 0x0F, + 10, // 10 ms delay + ST77XX_CASET, 4, // 15: Column addr set, 4 args, no delay: + 0x00, 0x02, // XSTART = 2 + 0x00, 0x81, // XEND = 129 + ST77XX_RASET, 4, // 16: Row addr set, 4 args, no delay: + 0x00, 0x02, // XSTART = 1 + 0x00, 0x81, // XEND = 160 + ST77XX_NORON, ST_CMD_DELAY, // 17: Normal display on, no args, w/delay + 10, // 10 ms delay + ST77XX_DISPON, ST_CMD_DELAY, // 18: Main screen turn on, no args, delay + 255 }, // 255 = max (500 ms) delay + + Rcmd1[] = { // 7735R init, part 1 (red or green tab) + 15, // 15 commands in list: + ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, 0 args, w/delay + 150, // 150 ms delay + ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, 0 args, w/delay + 255, // 500 ms delay + ST7735_FRMCTR1, 3, // 3: Framerate ctrl - normal mode, 3 arg: + 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) + ST7735_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args: + 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) + ST7735_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args: + 0x01, 0x2C, 0x2D, // Dot inversion mode + 0x01, 0x2C, 0x2D, // Line inversion mode + ST7735_INVCTR, 1, // 6: Display inversion ctrl, 1 arg: + 0x07, // No inversion + ST7735_PWCTR1, 3, // 7: Power control, 3 args, no delay: + 0xA2, + 0x02, // -4.6V + 0x84, // AUTO mode + ST7735_PWCTR2, 1, // 8: Power control, 1 arg, no delay: + 0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD + ST7735_PWCTR3, 2, // 9: Power control, 2 args, no delay: + 0x0A, // Opamp current small + 0x00, // Boost frequency + ST7735_PWCTR4, 2, // 10: Power control, 2 args, no delay: + 0x8A, // BCLK/2, + 0x2A, // opamp current small & medium low + ST7735_PWCTR5, 2, // 11: Power control, 2 args, no delay: + 0x8A, 0xEE, + ST7735_VMCTR1, 1, // 12: Power control, 1 arg, no delay: + 0x0E, + ST77XX_INVOFF, 0, // 13: Don't invert display, no args + ST77XX_MADCTL, 1, // 14: Mem access ctl (directions), 1 arg: + 0xC8, // row/col addr, bottom-top refresh + ST77XX_COLMOD, 1, // 15: set color mode, 1 arg, no delay: + 0x05 }, // 16-bit color + + Rcmd2green[] = { // 7735R init, part 2 (green tab only) + 2, // 2 commands in list: + ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: + 0x00, 0x02, // XSTART = 0 + 0x00, 0x7F+0x02, // XEND = 127 + ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: + 0x00, 0x01, // XSTART = 0 + 0x00, 0x9F+0x01 }, // XEND = 159 + + Rcmd2red[] = { // 7735R init, part 2 (red tab only) + 2, // 2 commands in list: + ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 0x7F, // XEND = 127 + ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 0x9F }, // XEND = 159 + + Rcmd2green144[] = { // 7735R init, part 2 (green 1.44 tab) + 2, // 2 commands in list: + ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 0x7F, // XEND = 127 + ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 0x7F }, // XEND = 127 + + Rcmd2green160x80[] = { // 7735R init, part 2 (mini 160x80) + 2, // 2 commands in list: + ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 0x4F, // XEND = 79 + ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 0x9F }, // XEND = 159 + + Rcmd2green160x80plugin[] = { // 7735R init, part 2 (mini 160x80 with plugin FPC) + 3, // 3 commands in list: + ST77XX_INVON, 0, // 1: Display is inverted + ST77XX_CASET, 4, // 2: Column addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 0x4F, // XEND = 79 + ST77XX_RASET, 4, // 3: Row addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 0x9F }, // XEND = 159 + + Rcmd3[] = { // 7735R init, part 3 (red or green tab) + 4, // 4 commands in list: + ST7735_GMCTRP1, 16 , // 1: Gamma Adjustments (pos. polarity), 16 args + delay: + 0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides + 0x37, 0x32, 0x29, 0x2d, // accurate colors) + 0x29, 0x25, 0x2B, 0x39, + 0x00, 0x01, 0x03, 0x10, + ST7735_GMCTRN1, 16 , // 2: Gamma Adjustments (neg. polarity), 16 args + delay: + 0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides + 0x2E, 0x2C, 0x29, 0x2D, // accurate colors) + 0x2E, 0x2E, 0x37, 0x3F, + 0x00, 0x00, 0x02, 0x10, + ST77XX_NORON, ST_CMD_DELAY, // 3: Normal display on, no args, w/delay + 10, // 10 ms delay + ST77XX_DISPON, ST_CMD_DELAY, // 4: Main screen turn on, no args w/delay + 100 }; // 100 ms delay + +static int16_t _xstart = 0; ///< Internal framebuffer X offset +static int16_t _ystart = 0; ///< Internal framebuffer Y offset +static uint8_t _colstart = 0; ///< Some displays need this changed to offset +static uint8_t _rowstart = 0; ///< Some displays need this changed to offset +static uint8_t rotation = 0; +static int16_t _width = 0; ///< Display width as modified by current rotation +static int16_t _height = 0; ///< Display height as modified by current rotation + +static void set_CS(uint8_t level) { + //if (_cs != (uint8_t) -1) { + digitalWrite(PIN_TFT_CS, level); + //} +} +static void sendCommand(uint8_t com) { + set_CS(HIGH); + digitalWrite(PIN_TFT_DC, LOW); + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(com); + _spi->endTransaction(); + set_CS(HIGH); + digitalWrite(PIN_TFT_DC, HIGH); +} + +static void WriteData(uint8_t data) { + digitalWrite(PIN_TFT_CS, LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(data); + _spi->endTransaction(); + digitalWrite(PIN_TFT_CS, HIGH); +} +static void SPI_WRITE32(uint32_t l) { + _spi->transfer(l >> 24); + _spi->transfer(l >> 16); + _spi->transfer(l >> 8); + _spi->transfer(l); +} +static void writeCommand(uint8_t cmd) { + digitalWrite(PIN_TFT_DC, LOW); + _spi->transfer(cmd); + digitalWrite(PIN_TFT_DC, HIGH); +} + +static void displayInit(const uint8_t *addr) { + uint8_t numCommands, cmd, numArgs; + uint16_t ms; + + numCommands = pgm_read_byte(addr++); // Number of commands to follow + while (numCommands--) { // For each command... + cmd = pgm_read_byte(addr++); // Read command + numArgs = pgm_read_byte(addr++); // Number of args to follow + ms = numArgs & ST_CMD_DELAY; // If hibit set, delay follows args + numArgs &= ~ST_CMD_DELAY; // Mask out delay bit + sendCommand(cmd); + for (int k = 0; k < numArgs; k++) { + WriteData(addr[k]); + } + addr += numArgs; + + if (ms) { + ms = pgm_read_byte(addr++); // Read post-command delay time (ms) + if (ms == 255) + ms = 500; // If 255, delay for 500 ms + delay(ms); + } + } +} + +static void setRotation(uint8_t m) { + uint8_t madctl = 0; + + rotation = m & 3; // can't be higher than 3 + + switch (rotation) { + case 0: + madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST7735_MADCTL_BGR; + + _height = 160; + _width = 80; + _xstart = _colstart; + _ystart = _rowstart; + break; + case 1: + madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR; + + _width = 160; + _height = 80; + _ystart = _colstart; + _xstart = _rowstart; + break; + case 2: + madctl = ST7735_MADCTL_BGR; + + _height = 160; + _width = 80; + _xstart = _colstart; + _ystart = _rowstart; + break; + case 3: + madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR; + + _width = 160; + _height = 80; + _ystart = _colstart; + _xstart = _rowstart; + break; + } + + sendCommand(ST77XX_MADCTL); + WriteData(madctl); +} + +static void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + x += _xstart; + y += _ystart; + uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); + uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); + + writeCommand(ST77XX_CASET); // Column addr set + SPI_WRITE32(xa); + + writeCommand(ST77XX_RASET); // Row addr set + SPI_WRITE32(ya); + + writeCommand(ST77XX_RAMWR); // write to RAM +} #define SCALE_X 1.25f // 160 / 128 #define SCALE_Y 1.25f // 80 / 64 +static TFT_eSprite *sprite = NULL; + bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { return true; /* @@ -16,35 +408,69 @@ bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { */ } +#ifndef PIN_TFT_LEDA_CTL_ACTIVE + #define PIN_TFT_LEDA_CTL_ACTIVE HIGH +#endif + bool ST7735Display::begin() { if (!_isOn) { if (_peripher_power) _peripher_power->claim(); - pinMode(PIN_TFT_LEDA_CTL, OUTPUT); -#if defined(PIN_TFT_LEDA_CTL_ACTIVE) - digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); + delay(3000); // TEMP!! + pinMode(PIN_TFT_RST, OUTPUT); + pinMode(PIN_TFT_CS, OUTPUT); + pinMode(PIN_TFT_DC, OUTPUT); + + // Pulse Reset low for 10ms + digitalWrite(PIN_TFT_RST, HIGH); + delay(1); + digitalWrite(PIN_TFT_RST, LOW); + delay(10); + digitalWrite(PIN_TFT_RST, HIGH); +#ifdef ESP_PLATFORM + _spi->begin(_clk,_miso,_mosi,-1); #else - digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + _spi->begin(); #endif + _spi->setClockDivider(SPI_CLOCK_DIV2); + + pinMode(PIN_TFT_LEDA_CTL, OUTPUT); + digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); digitalWrite(PIN_TFT_RST, HIGH); -#if defined(HELTEC_T1) - display.initR(INITR_MINI160x80); - display.setRotation(DISPLAY_ROTATION); -#elif defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096) - display.initR(INITR_MINI160x80); - display.setRotation(DISPLAY_ROTATION); - uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR - display.sendCommand(ST77XX_MADCTL, &madctl, 1); -#else - display.initR(INITR_MINI160x80_PLUGIN); - display.setRotation(DISPLAY_ROTATION); + displayInit(Rcmd1); + + _height = 80; + _width = 160; + _colstart = 24; + _rowstart = 0; + +#if defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096) + displayInit(Rcmd2green160x80); + //uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR + //display.sendCommand(ST77XX_MADCTL, &madctl, 1); #endif - display.setSPISpeed(40000000); - display.fillScreen(ST77XX_BLACK); - display.setTextColor(ST77XX_WHITE); - display.setTextSize(2); - display.cp437(true); // Use full 256 char 'Code Page 437' font + + displayInit(Rcmd3); + + setRotation(DISPLAY_ROTATION); + + sendCommand(ST77XX_DISPON); + + if (!sprite) { + // alloc offscreen canvas + sprite = new TFT_eSprite(&lcd); + if (sprite) { + if (sprite->createSprite(160, 80)) { + sprite->fillScreen(ST77XX_BLACK); + sprite->setTextColor(curr_color = ST77XX_WHITE); + } else { + Serial.printf("ST7735Display: failed to alloc canvas pixels"); + } + } else { + Serial.printf("ST7735Display: failed to alloc canvas"); + } + } _isOn = true; } @@ -52,17 +478,22 @@ bool ST7735Display::begin() { } void ST7735Display::turnOn() { - ST7735Display::begin(); + if (!_isOn) { + sendCommand(ST77XX_DISPON); + + // Now turn on the backlight + digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); + _isOn = true; + } } void ST7735Display::turnOff() { if (_isOn) { - digitalWrite(PIN_TFT_RST, LOW); -#if defined(PIN_TFT_LEDA_CTL_ACTIVE) + sendCommand(ST77XX_DISPOFF); + + //digitalWrite(PIN_TFT_RST, LOW); + // Now turn off the backlight digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE); -#else - digitalWrite(PIN_TFT_LEDA_CTL, LOW); -#endif _isOn = false; if (_peripher_power) _peripher_power->release(); @@ -70,78 +501,90 @@ void ST7735Display::turnOff() { } void ST7735Display::clear() { - //Serial.println("DBG: display.Clear"); - display.fillScreen(ST77XX_BLACK); + sprite->fillScreen(ST77XX_BLACK); } void ST7735Display::startFrame(Color bkg) { - display.fillScreen(0x00); - display.setTextColor(ST77XX_WHITE); - display.setTextSize(1); // This one affects size of Please wait... message - display.cp437(true); // Use full 256 char 'Code Page 437' font + sprite->fillScreen(ST77XX_BLACK); + sprite->setTextColor(curr_color = ST77XX_WHITE); + //sprite->setFreeFont(&FreeSans7pt7b); + sprite->setTextSize(1); // This one affects size of Please wait... message + //sprite->cp437(true); // Use full 256 char 'Code Page 437' font } void ST7735Display::setTextSize(int sz) { - display.setTextSize(sz); + sprite->setTextSize(sz); } void ST7735Display::setColor(Color c) { switch (c) { case DisplayDriver::DARK : - _color = ST77XX_BLACK; + curr_color = ST77XX_BLACK; break; case DisplayDriver::LIGHT : - _color = ST77XX_WHITE; + curr_color = ST77XX_WHITE; break; case DisplayDriver::RED : - _color = ST77XX_RED; + curr_color = ST77XX_RED; break; case DisplayDriver::GREEN : - _color = ST77XX_GREEN; + curr_color = ST77XX_GREEN; break; case DisplayDriver::BLUE : - _color = ST77XX_BLUE; + curr_color = ST77XX_BLUE; break; case DisplayDriver::YELLOW : - _color = ST77XX_YELLOW; + curr_color = ST77XX_YELLOW; break; case DisplayDriver::ORANGE : - _color = ST77XX_ORANGE; + curr_color = ST77XX_ORANGE; break; default: - _color = ST77XX_WHITE; + curr_color = ST77XX_WHITE; break; } - display.setTextColor(_color); + sprite->setTextColor(curr_color); } void ST7735Display::setCursor(int x, int y) { - display.setCursor(x*SCALE_X, y*SCALE_Y); + sprite->setCursor(x*SCALE_X, y*SCALE_Y); } void ST7735Display::print(const char* str) { - display.print(str); + sprite->print(str); } void ST7735Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color); + sprite->fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, curr_color); } void ST7735Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color); + sprite->drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, curr_color); } void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, _color); + sprite->drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, curr_color); } uint16_t ST7735Display::getTextWidth(const char* str) { - int16_t x1, y1; - uint16_t w, h; - display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); - return w / SCALE_X; + return sprite->textWidth(str) / SCALE_X; } void ST7735Display::endFrame() { - // display.display(); + // blit the canvas buffer to LCD + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + uint16_t x, y; + uint16_t* pixels = (uint16_t *) ((TFT_eSprite *) sprite)->getPointer(); + for (y = 0; y < 80; y++, pixels += 160) { + setAddrWindow(0, y, 160, 1); +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixels, NULL, 2 * 160); +#else + _spi->transfer(pixels, NULL, 2 * 160); +#endif + } + + _spi->endTransaction(); + set_CS(HIGH); } diff --git a/src/helpers/ui/ST7735Display.h b/src/helpers/ui/ST7735Display.h index 94797503..08778efd 100644 --- a/src/helpers/ui/ST7735Display.h +++ b/src/helpers/ui/ST7735Display.h @@ -3,28 +3,25 @@ #include "DisplayDriver.h" #include #include -#include -#include +#include "TFT_eSPI.h" #include class ST7735Display : public DisplayDriver { - Adafruit_ST7735 display; bool _isOn; - uint16_t _color; RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: #ifdef USE_PIN_TFT ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), - display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST), + // display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST), _peripher_power(peripher_power) { _isOn = false; } #else ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), - display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), + // display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), _peripher_power(peripher_power) { _isOn = false; diff --git a/variants/heltec_t096/platformio.ini b/variants/heltec_t096/platformio.ini index e820bf58..0a062f00 100644 --- a/variants/heltec_t096/platformio.ini +++ b/variants/heltec_t096/platformio.ini @@ -51,7 +51,7 @@ build_src_filter = ${nrf52_base.build_src_filter} lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + bodmer/TFT_eSPI @ ^2.4.31 debug_tool = jlink upload_protocol = nrfutil From 4fafcd0e23e95d44d2b4ba3f3d7282b1439fd51e Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:21:09 +1000 Subject: [PATCH 41/57] Lilygo T-Echo Lite - Enable nRF52840 DC-DC - board idle usage reduced from ~12mA to ~7mA --- variants/lilygo_techo_lite/TechoBoard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/lilygo_techo_lite/TechoBoard.cpp b/variants/lilygo_techo_lite/TechoBoard.cpp index a11d31b2..e0b69f98 100644 --- a/variants/lilygo_techo_lite/TechoBoard.cpp +++ b/variants/lilygo_techo_lite/TechoBoard.cpp @@ -6,7 +6,7 @@ #ifdef LILYGO_TECHO void TechoBoard::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); // Configure battery measurement control BEFORE Wire.begin() // to ensure P0.02 is not claimed by another peripheral From 3ee2f778771a14d1606eb711b8030033cfca4a6f Mon Sep 17 00:00:00 2001 From: Florent Date: Mon, 6 Jul 2026 08:46:57 -0400 Subject: [PATCH 42/57] restore display and radio poweroff in ui --- examples/companion_radio/ui-new/UITask.cpp | 3 +++ examples/companion_radio/ui-orig/UITask.cpp | 2 ++ examples/companion_radio/ui-tiny/UITask.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 403ea463..28591cc1 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -697,6 +697,9 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { + // still necessary until all boards are refactored to use poweroff + _display->turnOff(); + radio_driver.powerOff(); // Power off board including radio, display, GPS and components _board->powerOff(); } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index b48f6412..34a7342b 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -307,6 +307,8 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { + _display->turnOff(); + radio_driver.powerOff(); // Power off board including radio, display, GPS and components _board->powerOff(); } diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index 452c02d4..a6cbe9de 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -566,6 +566,8 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { + _display->turnOff(); + radio_driver.powerOff(); // Power off board including radio, display, GPS and components _board->powerOff(); } From fec88e13009fe215e1bebcc09a6c85196ff6ac0c Mon Sep 17 00:00:00 2001 From: Florent Date: Mon, 6 Jul 2026 12:04:42 -0400 Subject: [PATCH 43/57] fix tenstar c3 repeater build --- variants/tenstar_c3/target.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/tenstar_c3/target.h b/variants/tenstar_c3/target.h index b3ee6d17..7839fa97 100644 --- a/variants/tenstar_c3/target.h +++ b/variants/tenstar_c3/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include <../variants/xiao_c3/XiaoC3Board.h> #include #include #include From 2c9ab2fe5e817a48bd4db6efa8b6ad43c631ddfe Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 7 Jul 2026 13:54:14 +1000 Subject: [PATCH 44/57] * timing fixes (courtesy of Taco) --- src/helpers/ui/ST7735Display.cpp | 107 ++++++++++++++++--------------- src/helpers/ui/ST7735Display.h | 3 + 2 files changed, 60 insertions(+), 50 deletions(-) diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index 2b9f3ebb..aad12d1b 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -168,11 +168,11 @@ static const uint8_t PROGMEM 255 }, // 255 = max (500 ms) delay Rcmd1[] = { // 7735R init, part 1 (red or green tab) - 15, // 15 commands in list: - ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, 0 args, w/delay - 150, // 150 ms delay + 14, // 14 commands in list: + /*ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, 0 args, w/delay + 150, */ // 150 ms delay ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, 0 args, w/delay - 255, // 500 ms delay + 120, // 120 ms delay ST7735_FRMCTR1, 3, // 3: Framerate ctrl - normal mode, 3 arg: 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) ST7735_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args: @@ -251,7 +251,7 @@ static const uint8_t PROGMEM 0x00, 0x9F }, // XEND = 159 Rcmd3[] = { // 7735R init, part 3 (red or green tab) - 4, // 4 commands in list: + 2, // 2 commands in list: ST7735_GMCTRP1, 16 , // 1: Gamma Adjustments (pos. polarity), 16 args + delay: 0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides 0x37, 0x32, 0x29, 0x2d, // accurate colors) @@ -261,11 +261,7 @@ static const uint8_t PROGMEM 0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides 0x2E, 0x2C, 0x29, 0x2D, // accurate colors) 0x2E, 0x2E, 0x37, 0x3F, - 0x00, 0x00, 0x02, 0x10, - ST77XX_NORON, ST_CMD_DELAY, // 3: Normal display on, no args, w/delay - 10, // 10 ms delay - ST77XX_DISPON, ST_CMD_DELAY, // 4: Main screen turn on, no args w/delay - 100 }; // 100 ms delay + 0x00, 0x00, 0x02, 0x10 }; // 100 ms delay static int16_t _xstart = 0; ///< Internal framebuffer X offset static int16_t _ystart = 0; ///< Internal framebuffer Y offset @@ -413,20 +409,30 @@ bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { #endif bool ST7735Display::begin() { + if (!sprite) { + // alloc offscreen canvas + sprite = new TFT_eSprite(&lcd); + if (sprite) { + if (sprite->createSprite(160, 80)) { + sprite->fillScreen(ST77XX_BLACK); + sprite->setTextColor(curr_color = ST77XX_WHITE); + } else { + Serial.printf("ST7735Display: failed to alloc canvas pixels"); + } + } else { + Serial.printf("ST7735Display: failed to alloc canvas"); + } + } + if (!_isOn) { if (_peripher_power) _peripher_power->claim(); - delay(3000); // TEMP!! + delay(100); // TEMP!! pinMode(PIN_TFT_RST, OUTPUT); pinMode(PIN_TFT_CS, OUTPUT); pinMode(PIN_TFT_DC, OUTPUT); + pinMode(PIN_TFT_LEDA_CTL, OUTPUT); - // Pulse Reset low for 10ms - digitalWrite(PIN_TFT_RST, HIGH); - delay(1); - digitalWrite(PIN_TFT_RST, LOW); - delay(10); - digitalWrite(PIN_TFT_RST, HIGH); #ifdef ESP_PLATFORM _spi->begin(_clk,_miso,_mosi,-1); #else @@ -434,55 +440,56 @@ bool ST7735Display::begin() { #endif _spi->setClockDivider(SPI_CLOCK_DIV2); - pinMode(PIN_TFT_LEDA_CTL, OUTPUT); - digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); - digitalWrite(PIN_TFT_RST, HIGH); - - displayInit(Rcmd1); - _height = 80; _width = 160; _colstart = 24; _rowstart = 0; -#if defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096) - displayInit(Rcmd2green160x80); - //uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR - //display.sendCommand(ST77XX_MADCTL, &madctl, 1); -#endif - - displayInit(Rcmd3); - - setRotation(DISPLAY_ROTATION); - + _resetAndInit(); + sendCommand(ST77XX_DISPON); - - if (!sprite) { - // alloc offscreen canvas - sprite = new TFT_eSprite(&lcd); - if (sprite) { - if (sprite->createSprite(160, 80)) { - sprite->fillScreen(ST77XX_BLACK); - sprite->setTextColor(curr_color = ST77XX_WHITE); - } else { - Serial.printf("ST7735Display: failed to alloc canvas pixels"); - } - } else { - Serial.printf("ST7735Display: failed to alloc canvas"); - } - } _isOn = true; } return true; } +void ST7735Display::_resetAndInit() { + // Pulse Reset low for 10ms + digitalWrite(PIN_TFT_RST, HIGH); + delay(2); + digitalWrite(PIN_TFT_RST, LOW); + delay(10); + digitalWrite(PIN_TFT_RST, HIGH); + delay(2); + + // run init commands + displayInit(Rcmd1); +#if defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096) + displayInit(Rcmd2green160x80); + //uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR + //display.sendCommand(ST77XX_MADCTL, &madctl, 1); +#endif + displayInit(Rcmd3); + setRotation(DISPLAY_ROTATION); + + // clear the buffer before display on + sprite->fillScreen(ST77XX_BLACK); + endFrame(); + + // turn on backlight + digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); + +} + void ST7735Display::turnOn() { if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _resetAndInit(); sendCommand(ST77XX_DISPON); // Now turn on the backlight - digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); + // digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE); _isOn = true; } } @@ -507,7 +514,7 @@ void ST7735Display::clear() { void ST7735Display::startFrame(Color bkg) { sprite->fillScreen(ST77XX_BLACK); sprite->setTextColor(curr_color = ST77XX_WHITE); - //sprite->setFreeFont(&FreeSans7pt7b); + sprite->setFreeFont(); sprite->setTextSize(1); // This one affects size of Please wait... message //sprite->cp437(true); // Use full 256 char 'Code Page 437' font } diff --git a/src/helpers/ui/ST7735Display.h b/src/helpers/ui/ST7735Display.h index 08778efd..68c83db2 100644 --- a/src/helpers/ui/ST7735Display.h +++ b/src/helpers/ui/ST7735Display.h @@ -43,4 +43,7 @@ public: void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; uint16_t getTextWidth(const char* str) override; void endFrame() override; + +protected: + void _resetAndInit(); }; From 7f8522a3625a33a49599ad022695fe752763a06f Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:12:59 +1000 Subject: [PATCH 45/57] Heltec T114 - nRF52840 DC-DC Regulator - Enable DC-DC regulator on nRF52840 for Heltec T114 board (idle current reduced from ~12mA to ~9mA) --- variants/heltec_t114/T114Board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index c03d39af..51db72fc 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -33,7 +33,7 @@ void T114Board::initiateShutdown(uint8_t reason) { #endif // NRF52_POWER_MANAGEMENT void T114Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT_READ, INPUT); From 4605d3b6ce7a89806a35acad9359c0caf7d2a27e Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 9 Jul 2026 16:31:28 +0200 Subject: [PATCH 46/57] fix misspelled RF switch pin macros in SX1268/LLCC68/SX1276 radio wrappers --- src/helpers/radiolib/CustomLLCC68.h | 8 ++++---- src/helpers/radiolib/CustomSX1268.h | 8 ++++---- src/helpers/radiolib/CustomSX1276.h | 12 ++++++------ variants/lilygo_t3s3_sx1276/platformio.ini | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/helpers/radiolib/CustomLLCC68.h b/src/helpers/radiolib/CustomLLCC68.h index 82f886c3..686b09ec 100644 --- a/src/helpers/radiolib/CustomLLCC68.h +++ b/src/helpers/radiolib/CustomLLCC68.h @@ -66,11 +66,11 @@ class CustomLLCC68 : public LLCC68 { setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); #endif #if defined(SX126X_RXEN) || defined(SX126X_TXEN) - #ifndef SX1262X_RXEN - #define SX1262X_RXEN RADIOLIB_NC + #ifndef SX126X_RXEN + #define SX126X_RXEN RADIOLIB_NC #endif - #ifndef SX1262X_TXEN - #define SX1262X_TXEN RADIOLIB_NC + #ifndef SX126X_TXEN + #define SX126X_TXEN RADIOLIB_NC #endif setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); #endif diff --git a/src/helpers/radiolib/CustomSX1268.h b/src/helpers/radiolib/CustomSX1268.h index cc541e49..0c6f828b 100644 --- a/src/helpers/radiolib/CustomSX1268.h +++ b/src/helpers/radiolib/CustomSX1268.h @@ -66,11 +66,11 @@ class CustomSX1268 : public SX1268 { setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); #endif #if defined(SX126X_RXEN) || defined(SX126X_TXEN) - #ifndef SX1262X_RXEN - #define SX1262X_RXEN RADIOLIB_NC + #ifndef SX126X_RXEN + #define SX126X_RXEN RADIOLIB_NC #endif - #ifndef SX1262X_TXEN - #define SX1262X_TXEN RADIOLIB_NC + #ifndef SX126X_TXEN + #define SX126X_TXEN RADIOLIB_NC #endif setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); #endif diff --git a/src/helpers/radiolib/CustomSX1276.h b/src/helpers/radiolib/CustomSX1276.h index bee25274..e6f3270b 100644 --- a/src/helpers/radiolib/CustomSX1276.h +++ b/src/helpers/radiolib/CustomSX1276.h @@ -50,14 +50,14 @@ class CustomSX1276 : public SX1276 { setCurrentLimit(SX127X_CURRENT_LIMIT); #endif - #if defined(SX176X_RXEN) || defined(SX176X_TXEN) - #ifndef SX176X_RXEN - #define SX176X_RXEN RADIOLIB_NC + #if defined(SX127X_RXEN) || defined(SX127X_TXEN) + #ifndef SX127X_RXEN + #define SX127X_RXEN RADIOLIB_NC #endif - #ifndef SX176X_TXEN - #define SX176X_TXEN RADIOLIB_NC + #ifndef SX127X_TXEN + #define SX127X_TXEN RADIOLIB_NC #endif - setRfSwitchPins(SX176X_RXEN, SX176X_TXEN); + setRfSwitchPins(SX127X_RXEN, SX127X_TXEN); #endif setCRC(1); diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index 5df63202..e579e91c 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -21,8 +21,8 @@ build_flags = -D RADIO_CLASS=CustomSX1276 -D WRAPPER_CLASS=CustomSX1276Wrapper -D SX127X_CURRENT_LIMIT=120 - -D SX176X_RXEN=21 - -D SX176X_TXEN=10 + -D SX127X_RXEN=21 + -D SX127X_TXEN=10 -D LORA_TX_POWER=20 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_t3s3_sx1276> From 8ef369058df018e5c41d7fea6a438f6ec180254d Mon Sep 17 00:00:00 2001 From: jirogit Date: Thu, 9 Jul 2026 19:59:05 -0700 Subject: [PATCH 47/57] fix(RegionMap): distinguish clean EOF from partial read in load() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously load() returned true unconditionally on file-open success, masking truncated or corrupt /regions2 files. Additionally, the first field of each entry record (r->id) used the same success-chaining pattern as subsequent fields, so a clean EOF at a record boundary set success=false and would have been indistinguishable from real corruption once the return value was fixed. The r->id read is now split out: n==0 is a clean EOF (break, success retains its prior value from the header read), n!=sizeof(r->id) is a partial read or corruption (break, success=false). load() now returns success instead of an unconditional true, so its return value reflects the actual parse outcome. Companion fix to #2372, which fixed the same return-true hardcoding in save(). (#1891 originally reported this on load() but was closed when #2372 landed — that PR only touched save(); this addresses the load() side.) --- src/helpers/RegionMap.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 36c1d19d..4667e003 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -93,13 +93,15 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) { while (num_regions < MAX_REGION_ENTRIES) { auto r = ®ions[num_regions]; - success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id); + int n = file.read((uint8_t *) &r->id, sizeof(r->id)); + if (n == 0) break; // clean EOF + success = (n == sizeof(r->id)); success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent); success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name); success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags); success = success && file.read(pad, sizeof(pad)) == sizeof(pad); - if (!success) break; // EOF + if (!success) break; // partial read or corruption if (r->id >= next_id) { // make sure next_id is valid next_id = r->id + 1; @@ -108,7 +110,7 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) { } } file.close(); - return true; + return success; } } return false; // failed From 47e1ce5bae10f4e1f54fcdf4bbfecec7a31fc8ac Mon Sep 17 00:00:00 2001 From: Alex Beal Date: Sun, 12 Jul 2026 15:50:43 -0600 Subject: [PATCH 48/57] Update #defines External watchdog support was added (but never merged) for MeshTower V1. The update's V2's #defines to follow the conventions set there and piggy back off the support already written for V1. --- variants/heltec_tower_v2/variant.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/variants/heltec_tower_v2/variant.h b/variants/heltec_tower_v2/variant.h index 352184b8..3525d9b4 100644 --- a/variants/heltec_tower_v2/variant.h +++ b/variants/heltec_tower_v2/variant.h @@ -82,10 +82,10 @@ #define PIN_SERIAL2_RX (-1) #define PIN_SERIAL2_TX (-1) -#define HAS_HARDWARE_WATCHDOG -#define HARDWARE_WATCHDOG_DONE (0 + 9) -#define HARDWARE_WATCHDOG_WAKE (0 + 10) -#define HARDWARE_WATCHDOG_TIMEOUT_MS (8 * 60 * 1000) +#define HAS_EXTERNAL_WATCHDOG +#define EXTERNAL_WATCHDOG_DONE_PIN (0 + 9) +#define EXTERNAL_WATCHDOG_WAKE_PIN (0 + 10) +#define EXTERNAL_WATCHDOG_FEED_INTERNAL_MS (8 * 60 * 1000) #define SERIAL_PRINT_PORT 0 From 6343d8d96e0dfdf940c76d7f740e267be5466e62 Mon Sep 17 00:00:00 2001 From: Jody Bentley Date: Sun, 12 Jul 2026 17:54:14 -0400 Subject: [PATCH 49/57] ThinkNode M6 repeater: disable leftover MESH_DEBUG/GPS_NMEA_DEBUG The M6 is the only board whose simple_repeater env ships with MESH_DEBUG=1 and GPS_NMEA_DEBUG=1 enabled. MESH_DEBUG=1 adds a 5-second boot delay (examples/simple_repeater/main.cpp) plus verbose per-packet serial prints on the hot RX/TX path; GPS_NMEA_DEBUG=1 echoes every GPS UART character to the serial console (MicroNMEALocationProvider.h). Every sibling repeater env (M1, M3, t1000-e, RAK, Heltec) ships these off, and the M6 room_server env in this same file already has them commented. Comment them out to match. --- variants/thinknode_m6/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini index 6fe90436..035476e7 100644 --- a/variants/thinknode_m6/platformio.ini +++ b/variants/thinknode_m6/platformio.ini @@ -48,8 +48,8 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 - -D GPS_NMEA_DEBUG=1 +; -D MESH_DEBUG=1 +; -D GPS_NMEA_DEBUG=1 build_src_filter = ${ThinkNode_M6.build_src_filter} +<../examples/simple_repeater/*.cpp> lib_deps = From eb97a375d3a804a12ec5d2e2cd85b69da28810bf Mon Sep 17 00:00:00 2001 From: Alex Beal Date: Sun, 12 Jul 2026 16:17:23 -0600 Subject: [PATCH 50/57] Add an instance of ExternalWatchdogManager to Tower V2 --- variants/heltec_tower_v2/target.cpp | 33 +++++++++++++++++++++++++++++ variants/heltec_tower_v2/target.h | 11 ++++++++++ variants/heltec_tower_v2/variant.h | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/variants/heltec_tower_v2/target.cpp b/variants/heltec_tower_v2/target.cpp index ad457354..ec8e7a17 100644 --- a/variants/heltec_tower_v2/target.cpp +++ b/variants/heltec_tower_v2/target.cpp @@ -14,6 +14,7 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +TowerV2ExternalWatchdog external_watchdog; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -29,3 +30,35 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); } + +bool TowerV2ExternalWatchdog::begin() { + last_feed_watchdog = 0; + pinMode(EXTERNAL_WATCHDOG_WAKE_PIN, INPUT); + pinMode(EXTERNAL_WATCHDOG_DONE_PIN, OUTPUT); + delay(1); + digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, LOW); + delay(1); + feed(); + return true; +} + +void TowerV2ExternalWatchdog::loop() { + if (millis() - last_feed_watchdog >= EXTERNAL_WATCHDOG_FEED_INTERVAL_MS) { + feed(); + } +} + +unsigned long TowerV2ExternalWatchdog::getIntervalMs() const { + unsigned long elapsed_ms = millis() - last_feed_watchdog; + if (elapsed_ms >= EXTERNAL_WATCHDOG_FEED_INTERVAL_MS) { + return 0; + } + return EXTERNAL_WATCHDOG_FEED_INTERVAL_MS - elapsed_ms; +} + +void TowerV2ExternalWatchdog::feed() { + digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, HIGH); + delay(1); + digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, LOW); + last_feed_watchdog = millis(); +} diff --git a/variants/heltec_tower_v2/target.h b/variants/heltec_tower_v2/target.h index 03719246..0b9de40c 100644 --- a/variants/heltec_tower_v2/target.h +++ b/variants/heltec_tower_v2/target.h @@ -8,16 +8,27 @@ #include #include #include +#include #ifdef DISPLAY_CLASS #include #include "helpers/ui/NullDisplayDriver.h" #endif +class TowerV2ExternalWatchdog : public ExternalWatchdogManager { +public: + TowerV2ExternalWatchdog() {} + bool begin() override; + void loop() override; + unsigned long getIntervalMs() const override; + void feed() override; +}; + extern HeltecTowerV2Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern EnvironmentSensorManager sensors; +extern TowerV2ExternalWatchdog external_watchdog; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; diff --git a/variants/heltec_tower_v2/variant.h b/variants/heltec_tower_v2/variant.h index 3525d9b4..14116c6f 100644 --- a/variants/heltec_tower_v2/variant.h +++ b/variants/heltec_tower_v2/variant.h @@ -85,7 +85,7 @@ #define HAS_EXTERNAL_WATCHDOG #define EXTERNAL_WATCHDOG_DONE_PIN (0 + 9) #define EXTERNAL_WATCHDOG_WAKE_PIN (0 + 10) -#define EXTERNAL_WATCHDOG_FEED_INTERNAL_MS (8 * 60 * 1000) +#define EXTERNAL_WATCHDOG_FEED_INTERVAL_MS (8 * 60 * 1000) #define SERIAL_PRINT_PORT 0 From 63731d3fbf56f798f2289ee3c91eef7ecf1bfe6e Mon Sep 17 00:00:00 2001 From: Jody Bentley Date: Sun, 12 Jul 2026 18:21:53 -0400 Subject: [PATCH 51/57] sensors: fix millis() rollover stall in GPS time-sync + minor cleanups next_check and next_gps_update stored a future millis() value in a signed long and compared with a naive '>'. After the ~24.8-day millis() sign flip the deadline sits above the wrapped millis(), so the block never runs again and GPS->RTC time-sync (and the location cache refresh) stall permanently until reboot. Switch to unsigned deadlines with the wrap-safe signed- difference compare '(long)(millis() - deadline) > 0', matching the idiom in Dispatcher::millisHasNowPassed. Also: reorder the MicroNMEALocationProvider ctor init-list to declaration order (silences -Wreorder) and drop the always-true 'if (_claims > 0)' guard in claim() (claim() always runs after _claims++, so it is >= 1). --- src/helpers/sensors/EnvironmentSensorManager.cpp | 4 ++-- src/helpers/sensors/MicroNMEALocationProvider.h | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 73842d9e..af09eff6 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -889,11 +889,11 @@ void EnvironmentSensorManager::stop_gps() { void EnvironmentSensorManager::loop() { #if ENV_INCLUDE_GPS - static long next_gps_update = 0; + static unsigned long next_gps_update = 0; if (gps_active) { _location->loop(); } - if (millis() > next_gps_update) { + if ((long)(millis() - next_gps_update) > 0) { if(gps_active){ #ifdef RAK_WISBLOCK_GPS diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index eec466d3..6b06259b 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -42,14 +42,14 @@ class MicroNMEALocationProvider : public LocationProvider { int8_t _claims = 0; int _pin_reset; int _pin_en; - long next_check = 0; + unsigned long next_check = 0; long time_valid = 0; unsigned long _last_time_sync = 0; static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes public : MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) : - _gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock), _peripher_power(peripher_power) { + nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _gps_serial(&ser), _peripher_power(peripher_power), _pin_reset(pin_reset), _pin_en(pin_en) { if (_pin_reset != -1) { pinMode(_pin_reset, OUTPUT); digitalWrite(_pin_reset, GPS_RESET_FORCE); @@ -62,9 +62,7 @@ public : void claim() { _claims++; - if (_claims > 0) { - if (_peripher_power) _peripher_power->claim(); - } + if (_peripher_power) _peripher_power->claim(); } void release() { @@ -143,7 +141,7 @@ public : if (!isValid()) time_valid = 0; - if (millis() > next_check) { + if ((long)(millis() - next_check) > 0) { next_check = millis() + 1000; // Re-enable time sync periodically when GPS has valid fix if (!_time_sync_needed && _clock != NULL && (millis() - _last_time_sync) > TIME_SYNC_INTERVAL) { From 1429d75f57efa3e7cf4e7228e95fcbeacfd74695 Mon Sep 17 00:00:00 2001 From: Jody Bentley Date: Sun, 12 Jul 2026 18:32:09 -0400 Subject: [PATCH 52/57] ThinkNode M6/M1 room_server: set ROOM_PASSWORD default Without ROOM_PASSWORD the guest password defaults to empty, and a client sending a blank password is granted read+write access (can post). Every other board's room_server env sets ROOM_PASSWORD; the ThinkNode M6 and M1 were the only two shipping an open room. Default them to "hello" to match the rest of the tree; operators can still change it at runtime with 'set guest.password'. --- variants/thinknode_m1/platformio.ini | 1 + variants/thinknode_m6/platformio.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 356edfee..617f9240 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -58,6 +58,7 @@ build_flags = -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 = ${ThinkNode_M1.build_src_filter} diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini index 6fe90436..012afe46 100644 --- a/variants/thinknode_m6/platformio.ini +++ b/variants/thinknode_m6/platformio.ini @@ -63,6 +63,7 @@ build_flags = -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 = ${ThinkNode_M6.build_src_filter} From 17b6662aa58d740667f08e0c0690bd586906c88d Mon Sep 17 00:00:00 2001 From: liamcottle Date: Mon, 13 Jul 2026 14:39:54 +1200 Subject: [PATCH 53/57] update radiolib to v7.7.1-6d893483 --- platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 47cc0ab8..f3ada133 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,8 @@ monitor_speed = 115200 lib_deps = SPI Wire - jgromes/RadioLib @ ^7.6.0 + ;jgromes/RadioLib @ ^7.7.1 + https://github.com/jgromes/RadioLib.git#6d8934836678d8894e3d556550475b37dce3e2b6 rweather/Crypto @ ^0.4.0 adafruit/RTClib @ ^2.1.3 melopero/Melopero RV3028 @ ^1.1.0 From 87bf371cd3f6081078af2107f0b46da39253f806 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Mon, 13 Jul 2026 17:40:39 +1200 Subject: [PATCH 54/57] initial support for thinknode m9 --- boards/thinknode_m9.json | 57 ++++++++ src/helpers/AutoDiscoverRTCClock.cpp | 1 + src/helpers/ui/ST7789Display.cpp | 14 ++ src/helpers/ui/ST7789Display.h | 4 +- variants/thinknode_m9/ThinkNodeM9Board.cpp | 23 +++ variants/thinknode_m9/ThinkNodeM9Board.h | 13 ++ variants/thinknode_m9/pins_arduino.h | 64 +++++++++ variants/thinknode_m9/platformio.ini | 160 +++++++++++++++++++++ variants/thinknode_m9/target.cpp | 78 ++++++++++ variants/thinknode_m9/target.h | 29 ++++ variants/thinknode_m9/variant.cpp | 7 + variants/thinknode_m9/variant.h | 114 +++++++++++++++ 12 files changed, 563 insertions(+), 1 deletion(-) create mode 100755 boards/thinknode_m9.json create mode 100644 variants/thinknode_m9/ThinkNodeM9Board.cpp create mode 100644 variants/thinknode_m9/ThinkNodeM9Board.h create mode 100755 variants/thinknode_m9/pins_arduino.h create mode 100755 variants/thinknode_m9/platformio.ini create mode 100644 variants/thinknode_m9/target.cpp create mode 100644 variants/thinknode_m9/target.h create mode 100644 variants/thinknode_m9/variant.cpp create mode 100755 variants/thinknode_m9/variant.h diff --git a/boards/thinknode_m9.json b/boards/thinknode_m9.json new file mode 100755 index 00000000..31ebdbb9 --- /dev/null +++ b/boards/thinknode_m9.json @@ -0,0 +1,57 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi", + "partitions": "default_16MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=0", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=0" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "qio_opi", + "hwids": [ + [ + "0x303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "ELECROW-ThinkNode-M9" + }, + "connectivity": [ + "wifi", + "bluetooth", + "lora" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "elecrow-thinknode-m9", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 524288, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.elecrow.com/thinknode-m1-meshtastic-lora-signal-transceiver-powered-by-nrf52840-with-154-screen-support-gps.html", + "vendor": "ELECROW" +} \ No newline at end of file diff --git a/src/helpers/AutoDiscoverRTCClock.cpp b/src/helpers/AutoDiscoverRTCClock.cpp index a310539b..af08fddb 100644 --- a/src/helpers/AutoDiscoverRTCClock.cpp +++ b/src/helpers/AutoDiscoverRTCClock.cpp @@ -42,6 +42,7 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) { } if (i2c_probe(wire, PCF8563_ADDRESS)) { + MESH_DEBUG_PRINTLN("PCF8563: Found"); rtc_8563_success = rtc_8563.begin(&wire); } diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index f7d20b8a..98d69395 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -18,6 +18,14 @@ #define SCALE_Y 2.109375f // 135 / 64 #endif +#ifdef DISPLAY_SCALE_X + #define SCALE_X DISPLAY_SCALE_X +#endif + +#ifdef DISPLAY_SCALE_Y + #define SCALE_Y DISPLAY_SCALE_Y +#endif + bool ST7789Display::begin() { if(!_isOn) { pinMode(PIN_TFT_VDD_CTL, OUTPUT); @@ -32,6 +40,9 @@ bool ST7789Display::begin() { display.init(); display.landscapeScreen(); + #ifdef DISPLAY_FLIP_VERTICALLY + display.flipScreenVertically(); + #endif display.displayOn(); setCursor(0,0); @@ -49,6 +60,9 @@ void ST7789Display::turnOn() { // Re-initialize the display display.init(); display.displayOn(); + #ifdef DISPLAY_FLIP_VERTICALLY + display.flipScreenVertically(); + #endif delay(20); // Now turn on the backlight diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index cb56ff8a..9822a67d 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -14,8 +14,10 @@ class ST7789Display : public DisplayDriver { bool i2c_probe(TwoWire& wire, uint8_t addr); public: -#ifdef HELTEC_VISION_MASTER_T190 +#if defined(HELTEC_VISION_MASTER_T190) ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 320, 170,PIN_TFT_SDA,-1,PIN_TFT_SCL) {_isOn = false;} +#elif defined(THINKNODE_M9) + ST7789Display() : DisplayDriver(128, 64), display(&SPI, ST7789_RESET, ST7789_RS, ST7789_CS, GEOMETRY_RAWMODE, 320, 240, ST7789_SDA, ST7789_MISO, ST7789_SCK) {_isOn = false;} #else ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} #endif diff --git a/variants/thinknode_m9/ThinkNodeM9Board.cpp b/variants/thinknode_m9/ThinkNodeM9Board.cpp new file mode 100644 index 00000000..bf558639 --- /dev/null +++ b/variants/thinknode_m9/ThinkNodeM9Board.cpp @@ -0,0 +1,23 @@ +#include "ThinkNodeM9Board.h" + +void ThinkNodeM9Board::begin() { + + // power on screen + pinMode(VEXT_ENABLE, OUTPUT); + digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); + + ESP32Board::begin(); + +} + +void ThinkNodeM9Board::powerOff() { + enterDeepSleep(0); +} + +uint32_t ThinkNodeM9Board::getIRQGpio() { + return LORA_DIO0; +} + +const char* ThinkNodeM9Board::getManufacturerName() const { + return "Elecrow ThinkNode M9"; +} diff --git a/variants/thinknode_m9/ThinkNodeM9Board.h b/variants/thinknode_m9/ThinkNodeM9Board.h new file mode 100644 index 00000000..d5f68e05 --- /dev/null +++ b/variants/thinknode_m9/ThinkNodeM9Board.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +class ThinkNodeM9Board : public ESP32Board { + +public: + void begin(); + void powerOff() override; + const char* getManufacturerName() const override; + uint32_t getIRQGpio() override; +}; diff --git a/variants/thinknode_m9/pins_arduino.h b/variants/thinknode_m9/pins_arduino.h new file mode 100755 index 00000000..1886eea0 --- /dev/null +++ b/variants/thinknode_m9/pins_arduino.h @@ -0,0 +1,64 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +// Default SPI will be mapped to Radio +static const uint8_t SS = LORA_CS; +static const uint8_t SCK = SPI_SCK; +static const uint8_t MOSI = SPI_MOSI; +static const uint8_t MISO = SPI_MISO; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = I2C_SCL; +static const uint8_t SDA = I2C_SDA; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/thinknode_m9/platformio.ini b/variants/thinknode_m9/platformio.ini new file mode 100755 index 00000000..a7171726 --- /dev/null +++ b/variants/thinknode_m9/platformio.ini @@ -0,0 +1,160 @@ +[ThinkNode_M9] +extends = esp32_base +board = thinknode_m9 +board_check = true +board_build.partitions = default_16MB.csv +upload_protocol = esptool +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/thinknode_m9 + -I src/helpers/esp32 + -I src/helpers/sensors + -D THINKNODE_M9 + -D PIN_BUZZER=9 + -D PIN_BOARD_SCL=6 + -D PIN_BOARD_SDA=7 + -D P_LORA_NSS=39 + -D P_LORA_RESET=45 + -D P_LORA_BUSY=41 + -D P_LORA_SCLK=40 + -D P_LORA_MISO=38 + -D P_LORA_MOSI=47 + -D P_LORA_DIO_1=42 + -D USE_LR1110 + -D RF_SWITCH_TABLE + -D RADIO_CLASS=CustomLR1110 + -D WRAPPER_CLASS=CustomLR1110Wrapper + -D LR11X0_DIO_AS_RF_SWITCH=true + -D LR11X0_DIO3_TCXO_VOLTAGE=3.3 + -D LORA_TX_POWER=22 + -D DISPLAY_CLASS=ST7789Display + -D DISPLAY_FLIP_VERTICALLY=1 + -D DISPLAY_SCALE_X=2.5f ; 320 / 128 + -D DISPLAY_SCALE_Y=3.75f ; 240 / 64 + -D ST7789 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=17 + -D PIN_TFT_RST=14 + -D PIN_GPS_RX=3 + -D PIN_GPS_TX=2 + -D PIN_GPS_EN=11 + -D PIN_GPS_EN_ACTIVE=LOW + -D PIN_GPS_RESET=5 + -D PIN_GPS_RESET_ACTIVE=HIGH + -D GPS_BAUD_RATE=115200 + -D ENV_INCLUDE_GPS=1 + -D PIN_VBAT_READ=13 +build_src_filter = ${esp32_base.build_src_filter} + + + + + + + + + + + +<../variants/thinknode_m9> +lib_deps = ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit GFX Library @ ^1.12.1 + +[env:ThinkNode_M9_repeater_] +extends = ThinkNode_M9 +build_flags = + ${ThinkNode_M9.build_flags} + -D ADVERT_NAME='"ThinkNode M9 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 = ${ThinkNode_M9.build_src_filter} + +<../examples/simple_repeater/*.cpp> +lib_deps = + ${ThinkNode_M9.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M9_room_server_] +extends = ThinkNode_M9 +build_src_filter = ${ThinkNode_M9.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${ThinkNode_M9.build_flags} + -D ADVERT_NAME='"ThinkNode M9 Room Server"' + -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 +lib_deps = + ${ThinkNode_M9.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M9_companion_radio_ble_] +extends = ThinkNode_M9 +build_flags = + ${ThinkNode_M9.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + ; -D BLE_DEBUG_LOGGING=1 + ; -D MESH_PACKET_LOGGING=1 +build_src_filter = ${ThinkNode_M9.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M9.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M9_companion_radio_usb_] +extends = ThinkNode_M9 +build_flags = + ${ThinkNode_M9.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${ThinkNode_M9.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M9.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M9_companion_radio_wifi_] +extends = ThinkNode_M9 +build_flags = + ${ThinkNode_M9.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 + ; -D MESH_PACKET_LOGGING=1 +build_src_filter = ${ThinkNode_M9.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M9.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M9_kiss_modem] +extends = ThinkNode_M9 +build_src_filter = ${ThinkNode_M9.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/thinknode_m9/target.cpp b/variants/thinknode_m9/target.cpp new file mode 100644 index 00000000..3cbd7d75 --- /dev/null +++ b/variants/thinknode_m9/target.cpp @@ -0,0 +1,78 @@ +#include +#include "target.h" + +ThinkNodeM9Board board; + +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); + +#ifdef ENV_INCLUDE_GPS +#include +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + +#ifdef RF_SWITCH_TABLE +static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC, + RADIOLIB_NC, + 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, {HIGH, 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, +}; +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + +#ifdef LR11X0_DIO3_TCXO_VOLTAGE + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(2); + radio.explicitHeader(); + +#ifdef RF_SWITCH_TABLE + radio.setRfSwitchTable(rfswitch_dios, rfswitch_table); +#endif +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + + return true; // success +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} \ No newline at end of file diff --git a/variants/thinknode_m9/target.h b/variants/thinknode_m9/target.h new file mode 100644 index 00000000..f083a9f1 --- /dev/null +++ b/variants/thinknode_m9/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ThinkNodeM9Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +mesh::LocalIdentity radio_new_identity(); + diff --git a/variants/thinknode_m9/variant.cpp b/variants/thinknode_m9/variant.cpp new file mode 100644 index 00000000..4ac6a782 --- /dev/null +++ b/variants/thinknode_m9/variant.cpp @@ -0,0 +1,7 @@ +#include "variant.h" +#include + + +void initVariant() { + +} diff --git a/variants/thinknode_m9/variant.h b/variants/thinknode_m9/variant.h new file mode 100755 index 00000000..37f8b076 --- /dev/null +++ b/variants/thinknode_m9/variant.h @@ -0,0 +1,114 @@ +/*Power*/ +#define VEXT_ENABLE 18 +#define VEXT_ON_VALUE LOW +#define PIN_GPS_EN 11 +#define GPS_EN_ACTIVE LOW + +/*Wire Interface*/ +#define WIRE_INTERFACES_COUNT 2 +// I2C keybuttons +#define I2C_SCL1 21 +#define I2C_SDA1 20 +#define KB_INT 12 +// I2C peripheral` +#define I2C_SCL 6 +#define I2C_SDA 7 + +/*LED*/ +#define PIN_LED 13 +#define KB_LED 46 + +/*BUZZER*/ +#define PIN_BUZZER 9 + +/*CHARGE_CHECK*/ +#define DONE 8 +#define EXT_PWR_DETECT 1 +#define EXT_CHRG_DETECT 1 +#define EXT_CHRG_DETECT_VALUE LOW +/*GPS*/ +#define GPS_L76K +#define GPS_BAUDRATE 9600 +#define PIN_GPS_RESET 5 +#define PIN_GPS_PPS 4 +#define PIN_GPS_STANDBY 10 // An output to wake GPS, low means allow sleep, high means force wake +#define GPS_TX_PIN 3 +#define GPS_RX_PIN 2 +#define GPS_THREAD_INTERVAL 50 + +/*SPI*/ +#define SPI_MOSI 47 +#define SPI_SCK 40 +#define SPI_MISO 38 + +/*SD Card*/ +#define SDCARD_CS 48 +#define SD_SPI_FREQUENCY 80000000U + +/*Screen*/ +// #define USE_ST7789 1 +#define ST7789_CS 16 +#define ST7789_RS 15 +#define ST7789_TE 19 +#define ST7789_SDA SPI_MOSI // MOSI +#define ST7789_SCK SPI_SCK +#define ST7789_RESET 14 +#define ST7789_MISO SPI_MISO +#define ST7789_BUSY -1 +#define ST7789_BL 17 +#define ST7789_SPI_HOST SPI2_HOST +#define SPI_FREQUENCY 80000000 +#define SPI_READ_FREQUENCY 16000000 + +#define USE_TFTDISPLAY 1 +#define TFT_CS ST7789_CS +#define TFT_BL ST7789_BL +#define TFT_HEIGHT 320 +#define TFT_WIDTH 240 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 0 +#define TFT_PWM_FREQ 44000 +#define TFT_PWM_CHANNEL 7 +#define TFT_INVERT_LIGHT true +#define TFT_BACKLIGHT_ON LOW +#define SCREEN_ROTATE +#define SCREEN_TRANSITION_FRAMERATE 5 +#define BRIGHTNESS_DEFAULT 128 + +#define LGFX_PANEL ST7789 +#define DISPLAY_SIZE 320x240 +#define LGFX_ROTATION 0 +#define LGFX_CFG_HOST SPI2_HOST +#define LGFX_PIN_SCK ST7789_SCK +#define LGFX_PIN_MOSI ST7789_SDA +#define LGFX_PIN_DC ST7789_RS +#define LGFX_PIN_CS ST7789_CS +#define LGFX_PIN_BL ST7789_BL +#define LGFX_SCREEN_WIDTH TFT_WIDTH +#define LGFX_SCREEN_HEIGHT TFT_HEIGHT +#define LGFX_INVERT_LIGHT true +#define LGFX_PWM_FREQ 44000 +#define LGFX_PWM_CHANNEL 7 + +/*Lora radio*/ +#define LORA_SCK SPI_SCK +#define LORA_MISO SPI_MISO +#define LORA_MOSI SPI_MOSI +#define LORA_CS 39 +#define LORA_RESET 45 +#define LORA_DIO0 41 + +#define USE_LR1110 +#define LR1110_IRQ_PIN 42 +#define LR1110_NRESET_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_DIO0 +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO +#define LR11X0_DIO3_TCXO_VOLTAGE 3.3 +#define LR11X0_DIO_AS_RF_SWITCH + +/*RTC*/ +#define PCF8563_RTC 0x51 From 7e95c52972f237dd4b238efef4a5af13a2b72ea4 Mon Sep 17 00:00:00 2001 From: Will Dillon Date: Mon, 13 Jul 2026 05:44:55 -0700 Subject: [PATCH 55/57] Add StreamSensor allocation for GroupData Adds a small allocation for groupdata packets for the Meshcore firmware for the StreamSensor product. It was previously a LoRaWAN platform, and we've moved to Meshcore. Let me know when and how to demonstrate. --- docs/number_allocations.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/number_allocations.md b/docs/number_allocations.md index 2ccf950f..b8cc22a2 100644 --- a/docs/number_allocations.md +++ b/docs/number_allocations.md @@ -18,6 +18,7 @@ Once you have a working app/project, you need to be able to demonstrate it exist | 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open | | 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 | | FF00 - FFFF | -reserved for testing/dev- | | (add rows, inside the range 0100 - FEFF for custom apps) From 1bdacb324710f9f8c14e9dc50e8c79aab36e663b Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 13 Jul 2026 23:27:50 +1000 Subject: [PATCH 56/57] * trying to get Heltec Tracker and Tracker V2 working --- src/helpers/ui/ST7735Display.cpp | 9 +++++++-- variants/heltec_tracker/platformio.ini | 2 +- variants/heltec_tracker_v2/platformio.ini | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index aad12d1b..cb5737e1 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -102,7 +102,12 @@ static TFT_eSPI lcd = TFT_eSPI(160, 80); static uint32_t curr_color; -#define _spi (&SPI1) +#if defined(HELTEC_LORA_V3) || defined(HELTEC_TRACKER_V2) + static SPIClass tft_spi(SPI3_HOST); + #define _spi (&tft_spi) +#else + #define _spi (&SPI1) +#endif SPISettings _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); @@ -434,7 +439,7 @@ bool ST7735Display::begin() { pinMode(PIN_TFT_LEDA_CTL, OUTPUT); #ifdef ESP_PLATFORM - _spi->begin(_clk,_miso,_mosi,-1); + _spi->begin(PIN_TFT_SCL, -1 /* _miso */, PIN_TFT_SDA /* _mosi */, -1); #else _spi->begin(); #endif diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 69293d70..07d2e987 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -47,7 +47,7 @@ build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + bodmer/TFT_eSPI @ ^2.4.31 [env:Heltec_Wireless_Tracker_companion_radio_usb] extends = Heltec_tracker_base diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index d57c2113..f1fe9dd0 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -55,7 +55,7 @@ build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + bodmer/TFT_eSPI @ ^2.4.31 [env:heltec_tracker_v2_repeater] extends = Heltec_tracker_v2 From d30d8ed7483d45f5e0faa53e9e56a846e67e5145 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 14 Jul 2026 15:46:36 +1000 Subject: [PATCH 57/57] * HSPI fix --- src/helpers/ui/ST7735Display.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index cb5737e1..cdd15aeb 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -103,7 +103,7 @@ static TFT_eSPI lcd = TFT_eSPI(160, 80); static uint32_t curr_color; #if defined(HELTEC_LORA_V3) || defined(HELTEC_TRACKER_V2) - static SPIClass tft_spi(SPI3_HOST); + static SPIClass tft_spi(HSPI); #define _spi (&tft_spi) #else #define _spi (&SPI1)