From c89a0e9929f4247d40569f568ccb1d216cc66762 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 22 Jan 2026 15:03:40 +0800 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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 b975180fc88e441041ec52ffa49b36b0a215e1b0 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Thu, 25 Jun 2026 16:03:27 +1200 Subject: [PATCH 05/13] 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 4605d3b6ce7a89806a35acad9359c0caf7d2a27e Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 9 Jul 2026 16:31:28 +0200 Subject: [PATCH 06/13] 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 47e1ce5bae10f4e1f54fcdf4bbfecec7a31fc8ac Mon Sep 17 00:00:00 2001 From: Alex Beal Date: Sun, 12 Jul 2026 15:50:43 -0600 Subject: [PATCH 07/13] 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 08/13] 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 09/13] 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 10/13] 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 11/13] 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 12/13] 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 13/13] 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