From 0920dc66637cc9da0abc2228b9241c1abc5c1eb9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:23:45 +0200 Subject: [PATCH 01/53] Fix reversed GPS PINs on G2 and enable timesync --- variants/station_g2/platformio.ini | 4 ++-- variants/station_g2/target.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index bda8b7ae..bc00bae7 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -21,8 +21,8 @@ build_flags = -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=38 - -D PIN_GPS_RX=7 - -D PIN_GPS_TX=15 + -D PIN_GPS_RX=15 + -D PIN_GPS_TX=7 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index c2ee97e2..3f0c1404 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -17,7 +17,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 f3b20d5e70e67780decc64c69a7e34b5b14356cc Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:35:01 +0100 Subject: [PATCH 02/53] t114 gps --- variants/heltec_t114/platformio.ini | 5 +++++ variants/heltec_t114/target.cpp | 2 +- variants/heltec_t114/variant.h | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index c482a30a..91ca78cd 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -29,6 +29,11 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D DISPLAY_CLASS=NullDisplayDriver -D ST7789 + -D PIN_GPS_RX=39 + -D PIN_GPS_TX=37 + -D PIN_GPS_EN=21 + -D PIN_GPS_RESET=38 + -D PIN_GPS_RESET_ACTIVE=LOW build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/heltec_t114> diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index 5b786437..c3341103 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -11,7 +11,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); T114SensorManager sensors = T114SensorManager(nmea); #ifdef DISPLAY_CLASS diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index a0fd2e4f..b3f760bb 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -117,6 +117,8 @@ #define GPS_EN (21) #define GPS_RESET (38) +#define PIN_GPS_RX (39) // This is for bits going TOWARDS the GPS +#define PIN_GPS_TX (37) // This is for bits going TOWARDS the CPU //////////////////////////////////////////////////////////////////////////////// // TFT From c0a51aff66ee53949e6a499d270cd00e6809dab7 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:47:18 +0100 Subject: [PATCH 03/53] change ADC_MULTIPLIER to better reflect battery voltage --- variants/thinknode_m2/variant.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/variants/thinknode_m2/variant.h b/variants/thinknode_m2/variant.h index 928f56ec..223bfbb5 100644 --- a/variants/thinknode_m2/variant.h +++ b/variants/thinknode_m2/variant.h @@ -1,8 +1,8 @@ #define I2C_SCL 15 #define I2C_SDA 16 -#define PIN_VBAT_READ 17 +#define PIN_VBAT_READ 17 #define AREF_VOLTAGE (3.0) -#define ADC_MULTIPLIER (1.548F) +#define ADC_MULTIPLIER (1.509F) #define PIN_BUZZER 5 #define PIN_VEXT_EN_ACTIVE HIGH #define PIN_VEXT_EN 46 @@ -10,6 +10,3 @@ #define PIN_LED 6 #define PIN_STATUS_LED 6 #define PIN_PWRBTN 4 - - - From 429f82106bbb16a353d0aa5e86be8fbf18aa2972 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:48:57 +0100 Subject: [PATCH 04/53] tweak getBattMilliVolts to report battery more accurately --- variants/thinknode_m2/ThinknodeM2Board.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp index 3a2f067e..332f1f67 100644 --- a/variants/thinknode_m2/ThinknodeM2Board.cpp +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -19,14 +19,21 @@ void ThinknodeM2Board::begin() { enterDeepSleep(0); } - uint16_t ThinknodeM2Board::getBattMilliVolts() { + uint16_t ThinknodeM2Board::getBattMilliVolts() { analogReadResolution(12); - delay(10); - float volts = (analogRead(PIN_VBAT_READ) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096; - analogReadResolution(10); - return volts * 1000; - } + 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"; - } \ No newline at end of file + } From a0bf66f9d83dbe75353d665de827f503d533c600 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:50:21 +0100 Subject: [PATCH 05/53] Fix for display not coming on after poweron --- variants/thinknode_m2/ThinknodeM2Board.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp index 332f1f67..05965103 100644 --- a/variants/thinknode_m2/ThinknodeM2Board.cpp +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -3,12 +3,13 @@ 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_VEXT_EN, OUTPUT); // init display - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // pin needs to be high - delay(10); - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // need to do this twice. do not know why.. - pinMode(PIN_STATUS_LED, OUTPUT); // init power led + pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { From 00e0635ab5400c9fc78bf05c729ddb1f234f3a9a Mon Sep 17 00:00:00 2001 From: brad Date: Sat, 8 Nov 2025 20:05:46 -0600 Subject: [PATCH 06/53] add variant files for ikoka handheld (nrf52 with e22 radio) --- .../ikoka_handheld_nrf/IkokaNrf52Board.cpp | 100 ++++++++++++ variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 52 ++++++ variants/ikoka_handheld_nrf/platformio.ini | 103 ++++++++++++ variants/ikoka_handheld_nrf/target.cpp | 46 ++++++ variants/ikoka_handheld_nrf/target.h | 29 ++++ variants/ikoka_handheld_nrf/variant.cpp | 84 ++++++++++ variants/ikoka_handheld_nrf/variant.h | 148 ++++++++++++++++++ 7 files changed, 562 insertions(+) create mode 100644 variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp create mode 100644 variants/ikoka_handheld_nrf/IkokaNrf52Board.h create mode 100644 variants/ikoka_handheld_nrf/platformio.ini create mode 100644 variants/ikoka_handheld_nrf/target.cpp create mode 100644 variants/ikoka_handheld_nrf/target.h create mode 100644 variants/ikoka_handheld_nrf/variant.cpp create mode 100644 variants/ikoka_handheld_nrf/variant.h diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp new file mode 100644 index 00000000..44940b8f --- /dev/null +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp @@ -0,0 +1,100 @@ +#ifdef IKOKA_NRF52 + +#include +#include +#include + +#include "IkokaNrf52Board.h" + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void IkokaNrf52Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + // ensure we have pull ups on the screen i2c, this isn't always available + // in hardware and it should only be 20k ohms. Disable the pullups if we + // are using the rotated lcd breakout board + #if defined(DISPLAY_CLASS) && DISPLAY_ROTATION == 0 + pinMode(PIN_WIRE_SDA, INPUT_PULLUP); + pinMode(PIN_WIRE_SCL, INPUT_PULLUP); + #endif + + pinMode(PIN_VBAT, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, HIGH); + + // required button pullup is handled as part of button initilization + // in target.cpp + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +#endif + + delay(10); // give sx1262 some time to power up +} + +bool IkokaNrf52Board::startOTAUpdate(const char *id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("XIAO_NRF52_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + + return true; +} + +#endif diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h new file mode 100644 index 00000000..9dfc3833 --- /dev/null +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#ifdef IKOKA_NRF52 + +class IkokaNrf52Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + } +#endif + + uint16_t getBattMilliVolts() override { + // Please read befor going further ;) + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + + // We can't drive VBAT_ENABLE to HIGH as long + // as we don't know wether we are charging or not ... + // this is a 3mA loss (4/1500) + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char* getManufacturerName() const override { + return "Ikoka Handheld E22 30dBm (Xiao_nrf52)"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; + +#endif diff --git a/variants/ikoka_handheld_nrf/platformio.ini b/variants/ikoka_handheld_nrf/platformio.ini new file mode 100644 index 00000000..e4643c38 --- /dev/null +++ b/variants/ikoka_handheld_nrf/platformio.ini @@ -0,0 +1,103 @@ +[ikoka_nrf52] +extends = Xiao_nrf52 +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + densaugeo/base64 @ ~1.4.0 +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 + -I variants/ikoka_handheld_nrf + -UENV_INCLUDE_GPS + -D IKOKA_NRF52 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_TX_LED=11 + -D P_LORA_DIO_1=D1 + -D P_LORA_RESET=D2 + -D P_LORA_BUSY=D3 + -D P_LORA_NSS=D4 + -D SX126X_RXEN=D5 + -D SX126X_TXEN=RADIOLIB_NC + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/ikoka_handheld_nrf> + + + +# larger screen has a different driver, this is for the 0.96 inch +[ikoka_nrf52_ssd1306_companion] +lib_deps = ${ikoka_nrf52.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 +build_flags = ${ikoka_nrf52.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D DISPLAY_ROTATION=0 + -D PIN_WIRE_SCL=D6 + -D PIN_WIRE_SDA=D7 + -D PIN_USER_BTN=D0 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D QSPIFLASH=1 + -I examples/companion_radio/ui-new +build_src_filter = ${ikoka_nrf52.build_src_filter} + + + +<../examples/companion_radio/ui-new/UITask.cpp> + +<../examples/companion_radio/*.cpp> + +[env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_ble] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D BLE_PIN_CODE=123456 + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + + + +[env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_ble] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D BLE_PIN_CODE=123456 + -D LORA_TX_POWER=20 + -D DISPLAY_ROTATION=2 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + + + +[env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_usb] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + +[env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_usb] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D LORA_TX_POWER=20 + -D DISPLAY_ROTATION=2 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + +[env:ikoka_handheld_nrf_e22_30dbm_repeater] +extends = ikoka_nrf52 +build_flags = + ${ikoka_nrf52.build_flags} + -D ADVERT_NAME='"ikoka_handheld Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52.build_src_filter} + +<../examples/simple_repeater/*.cpp> + +[env:ikoka_handheld_nrf_e22_30dbm_room_server] +extends = ikoka_nrf52 +build_flags = + ${ikoka_nrf52.build_flags} + -D ADVERT_NAME='"ikoka_handheld Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52.build_src_filter} + +<../examples/simple_room_server/*.cpp> diff --git a/variants/ikoka_handheld_nrf/target.cpp b/variants/ikoka_handheld_nrf/target.cpp new file mode 100644 index 00000000..efa6669f --- /dev/null +++ b/variants/ikoka_handheld_nrf/target.cpp @@ -0,0 +1,46 @@ +#include +#include "target.h" +#include + +IkokaNrf52Board 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); + +EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); +#endif + + +bool radio_init() { + 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); // create new random identity +} diff --git a/variants/ikoka_handheld_nrf/target.h b/variants/ikoka_handheld_nrf/target.h new file mode 100644 index 00000000..a28ca81a --- /dev/null +++ b/variants/ikoka_handheld_nrf/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + #include + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + + +extern IkokaNrf52Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +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(); diff --git a/variants/ikoka_handheld_nrf/variant.cpp b/variants/ikoka_handheld_nrf/variant.cpp new file mode 100644 index 00000000..38a94c89 --- /dev/null +++ b/variants/ikoka_handheld_nrf/variant.cpp @@ -0,0 +1,84 @@ +#include "variant.h" + +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) + + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) + + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) + + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) + + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) + + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) + + // VBAT + 31, // D32 is P0.31 (VBAT) +}; + +void initVariant() { + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + // digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); + + // disable xiao charging current, the handheld uses a tp4056 to charge + // instead of the onboard xiao charging circuit. This charges at a max of + // 780ma instead of 100ma. In theory you could enable both, but in practice + // fire is scary. + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, HIGH); + + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); +} diff --git a/variants/ikoka_handheld_nrf/variant.h b/variants/ikoka_handheld_nrf/variant.h new file mode 100644 index 00000000..8e6a8ed1 --- /dev/null +++ b/variants/ikoka_handheld_nrf/variant.h @@ -0,0 +1,148 @@ +#ifndef _SEEED_XIAO_NRF52840_H_ +#define _SEEED_XIAO_NRF52840_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +//#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define PINS_COUNT (33) +#define NUM_DIGITAL_PINS (33) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (LED_RED) +#define LED_PWR (PINS_COUNT) +#define PIN_NEOPIXEL (PINS_COUNT) +#define NEOPIXEL_NUM (0) + +#define LED_BUILTIN (PIN_LED) + +#define LED_RED (11) +#define LED_GREEN (13) +#define LED_BLUE (12) + +#define LED_STATE_ON (1) // State when LED is litted + +// Buttons +#define PIN_BUTTON1 (PINS_COUNT) + +// Digital PINs +static const uint8_t D0 = 0 ; +static const uint8_t D1 = 1 ; +static const uint8_t D2 = 2 ; +static const uint8_t D3 = 3 ; +static const uint8_t D4 = 4 ; +static const uint8_t D5 = 5 ; +static const uint8_t D6 = 6 ; +static const uint8_t D7 = 7 ; +static const uint8_t D8 = 8 ; +static const uint8_t D9 = 9 ; +static const uint8_t D10 = 10; + +#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define PIN_CHARGING_CURRENT (22) // Battery Charging current + // https://wiki.seedstudio.com/XIAO_BLE#battery-charging-current +// Analog pins +#define PIN_A0 (0) +#define PIN_A1 (1) +#define PIN_A2 (2) +#define PIN_A3 (3) +#define PIN_A4 (4) +#define PIN_A5 (5) +#define PIN_VBAT (32) // Read the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define BAT_NOT_CHARGING (23) // LOW when charging + +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; + +#define ADC_RESOLUTION (12) + +// Other pins +#define PIN_NFC1 (30) +#define PIN_NFC2 (31) + +// Serial interfaces +#define PIN_SERIAL1_RX (7) +#define PIN_SERIAL1_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (9) +#define PIN_SPI_MOSI (10) +#define PIN_SPI_SCK (8) + +#define PIN_SPI1_MISO (25) +#define PIN_SPI1_MOSI (26) +#define PIN_SPI1_SCK (29) + +// Lora SPI is on SPI0 +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (6) // 4 and 5 are used for the sx1262 ! +#define PIN_WIRE_SCL (7) // use WIRE1_SDA + +static const uint8_t SDA = (6); +static const uint8_t SCL = (7); + +//#define PIN_WIRE1_SDA (17) +//#define PIN_WIRE1_SCL (16) +#define PIN_LSM6DS3TR_C_POWER (15) +#define PIN_LSM6DS3TR_C_INT1 (18) + +// PDM Interfaces +#define PIN_PDM_PWR (19) +#define PIN_PDM_CLK (20) +#define PIN_PDM_DIN (21) + +// QSPI Pins +#define PIN_QSPI_SCK (24) +#define PIN_QSPI_CS (25) +#define PIN_QSPI_IO0 (26) +#define PIN_QSPI_IO1 (27) +#define PIN_QSPI_IO2 (28) +#define PIN_QSPI_IO3 (29) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES (P25Q16H) +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From df4dab8509081a08010229af207ac2eae288bab8 Mon Sep 17 00:00:00 2001 From: agessaman Date: Fri, 7 Nov 2025 22:16:48 -0800 Subject: [PATCH 07/53] Add statistics commands and response handling in MyMesh - Introduced new commands for retrieving statistics: CMD_GET_STATS_CORE, CMD_GET_STATS_RADIO, and CMD_GET_STATS_PACKETS. - Implemented corresponding response handling methods to format and send statistics data. - Updated MyMesh constructor to initialize new member variables for managing statistics. - Included StatsFormatHelper for formatting statistics replies. --- examples/companion_radio/MyMesh.cpp | 62 ++++++++++++++++++++++++++++- examples/companion_radio/MyMesh.h | 8 ++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 598d535f..fa9edc8c 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -52,6 +52,9 @@ #define CMD_SEND_PATH_DISCOVERY_REQ 52 #define CMD_SET_FLOOD_SCOPE 54 // v8+ #define CMD_SEND_CONTROL_DATA 55 // v8+ +#define CMD_GET_STATS_CORE 56 +#define CMD_GET_STATS_RADIO 57 +#define CMD_GET_STATS_PACKETS 58 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -77,6 +80,9 @@ #define RESP_CODE_CUSTOM_VARS 21 #define RESP_CODE_ADVERT_PATH 22 #define RESP_CODE_TUNING_PARAMS 23 +#define RESP_CODE_STATS_CORE 24 +#define RESP_CODE_STATS_RADIO 25 +#define RESP_CODE_STATS_PACKETS 26 #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -704,7 +710,7 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui) - : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), + : BaseChatMesh(radio, *(_ms_clock = new ArduinoMillis()), rng, rtc, *(_pkt_mgr = new StaticPoolPacketManager(16)), tables), _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) { _iter_started = false; _cli_rescue = false; @@ -1529,6 +1535,45 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); } + } else if (cmd_frame[0] == CMD_GET_STATS_CORE) { + char json_reply[160]; + formatStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_CORE; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } + } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { + char json_reply[160]; + formatRadioStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_RADIO; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } + } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { + char json_reply[160]; + formatPacketStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_PACKETS; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { @@ -1565,6 +1610,21 @@ void MyMesh::enterCLIRescue() { Serial.println("========= CLI Rescue ========="); } +void MyMesh::formatStatsReply(char *reply) { + // Use StatsFormatHelper + // Note: err_flags is private in Dispatcher, so we use 0 + StatsFormatHelper::formatCoreStats(reply, board, *_ms_clock, 0, _pkt_mgr); +} + +void MyMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void MyMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + void MyMesh::checkCLIRescueCmd() { int len = strlen(cli_command); while (Serial.available() && len < sizeof(cli_command)-1) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index f2b56e5e..ef451d5d 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -69,6 +69,7 @@ #include #include +#include /* -------------------------------------------------------------------------------------- */ @@ -170,6 +171,11 @@ private: void checkCLIRescueCmd(); void checkSerialInterface(); + // Stats methods + void formatStatsReply(char *reply); + void formatRadioStatsReply(char *reply); + void formatPacketStatsReply(char *reply); + // helpers, short-cuts void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } @@ -178,6 +184,8 @@ private: private: DataStore* _store; NodePrefs _prefs; + mesh::PacketManager* _pkt_mgr; // stored for stats access + mesh::MillisecondClock* _ms_clock; // stored for stats access uint32_t pending_login; uint32_t pending_status; uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ From c9aa536ca632db7f28029ba1a4a66bc4bdccda92 Mon Sep 17 00:00:00 2001 From: agessaman Date: Sat, 8 Nov 2025 20:44:42 -0800 Subject: [PATCH 08/53] Reverted MyMesh constructor for simplicity. Updated formatStatsReply method to use new member variables for statistics handling. Removed excess variable creation --- examples/companion_radio/MyMesh.cpp | 5 ++--- examples/companion_radio/MyMesh.h | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index fa9edc8c..6fe165c5 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -710,7 +710,7 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui) - : BaseChatMesh(radio, *(_ms_clock = new ArduinoMillis()), rng, rtc, *(_pkt_mgr = new StaticPoolPacketManager(16)), tables), + : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) { _iter_started = false; _cli_rescue = false; @@ -1612,8 +1612,7 @@ void MyMesh::enterCLIRescue() { void MyMesh::formatStatsReply(char *reply) { // Use StatsFormatHelper - // Note: err_flags is private in Dispatcher, so we use 0 - StatsFormatHelper::formatCoreStats(reply, board, *_ms_clock, 0, _pkt_mgr); + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); } void MyMesh::formatRadioStatsReply(char *reply) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index ef451d5d..61a25e14 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -184,8 +184,6 @@ private: private: DataStore* _store; NodePrefs _prefs; - mesh::PacketManager* _pkt_mgr; // stored for stats access - mesh::MillisecondClock* _ms_clock; // stored for stats access uint32_t pending_login; uint32_t pending_status; uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ From 80d6dd4367c2882a5b576ed1394bb4c2151b290e Mon Sep 17 00:00:00 2001 From: agessaman Date: Sun, 9 Nov 2025 11:28:11 -0800 Subject: [PATCH 09/53] Update statistics handling to use binary frames instead of JSON formatting for consistency with other companion commands. Added documentation of frame structure with code examples. --- docs/stats_binary_frames.md | 225 ++++++++++++++++++++++++++++ examples/companion_radio/MyMesh.cpp | 62 ++++---- 2 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 docs/stats_binary_frames.md diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md new file mode 100644 index 00000000..d4327ea6 --- /dev/null +++ b/docs/stats_binary_frames.md @@ -0,0 +1,225 @@ +# Stats Binary Frame Structures + +Binary frame structures for companion radio stats commands. All multi-byte integers use little-endian byte order. + +## Command Codes + +| Command | Code | Description | +|---------|------|-------------| +| `CMD_GET_STATS_CORE` | 56 | Get core device statistics | +| `CMD_GET_STATS_RADIO` | 57 | Get radio statistics | +| `CMD_GET_STATS_PACKETS` | 58 | Get packet statistics | + +## Response Codes + +| Response | Code | Description | +|----------|------|-------------| +| `RESP_CODE_STATS_CORE` | 24 | Core stats response | +| `RESP_CODE_STATS_RADIO` | 25 | Radio stats response | +| `RESP_CODE_STATS_PACKETS` | 26 | Packet stats response | + +--- + +## RESP_CODE_STATS_CORE (24) + +**Total Frame Size:** 10 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | +| 3 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | +| 7 | 2 | uint16_t | errors | Error flags bitmask | - | +| 9 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | + +### Example Structure (C/C++) + +```c +struct StatsCore { + uint8_t response_code; // 0x18 + uint16_t battery_mv; + uint32_t uptime_secs; + uint16_t errors; + uint8_t queue_len; +} __attribute__((packed)); +``` + +--- + +## RESP_CODE_STATS_RADIO (25) + +**Total Frame Size:** 13 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x19` (25) | - | +| 1 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | +| 3 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | +| 4 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | +| 5 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | +| 9 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | + +### Example Structure (C/C++) + +```c +struct StatsRadio { + uint8_t response_code; // 0x19 + int16_t noise_floor; + int8_t last_rssi; + int8_t last_snr; // Divide by 4.0 to get actual SNR in dB + uint32_t tx_air_secs; + uint32_t rx_air_secs; +} __attribute__((packed)); +``` + +--- + +## RESP_CODE_STATS_PACKETS (26) + +**Total Frame Size:** 25 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x1A` (26) | - | +| 1 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | +| 5 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | +| 9 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | +| 13 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | +| 17 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | +| 21 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | + +### Notes + +- Counters are cumulative from boot and may wrap. +- `recv = flood_rx + direct_rx` +- `sent = flood_tx + direct_tx` + +### Example Structure (C/C++) + +```c +struct StatsPackets { + uint8_t response_code; // 0x1A + uint32_t recv; + uint32_t sent; + uint32_t flood_tx; + uint32_t direct_tx; + uint32_t flood_rx; + uint32_t direct_rx; +} __attribute__((packed)); +``` + +--- + +## Usage Example (Python) + +```python +import struct + +def parse_stats_core(frame): + """Parse RESP_CODE_STATS_CORE frame (10 bytes)""" + response_code, battery_mv, uptime_secs, errors, queue_len = \ + struct.unpack('writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + uint16_t battery_mv = board.getBattMilliVolts(); + uint32_t uptime_secs = _ms->getMillis() / 1000; + uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); + memcpy(&out_frame[i], &battery_mv, 2); i += 2; + memcpy(&out_frame[i], &uptime_secs, 4); i += 4; + memcpy(&out_frame[i], &_err_flags, 2); i += 2; + out_frame[i++] = queue_len; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { - char json_reply[160]; - formatRadioStatsReply(json_reply); int i = 0; out_frame[i++] = RESP_CODE_STATS_RADIO; - int json_len = strlen(json_reply); - if (i + json_len <= MAX_FRAME_SIZE) { - memcpy(&out_frame[i], json_reply, json_len); - i += json_len; - _serial->writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); + int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); + int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision + uint32_t tx_air_secs = getTotalAirTime() / 1000; + uint32_t rx_air_secs = getReceiveAirTime() / 1000; + memcpy(&out_frame[i], &noise_floor, 2); i += 2; + out_frame[i++] = last_rssi; + out_frame[i++] = last_snr; + memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; + memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { - char json_reply[160]; - formatPacketStatsReply(json_reply); int i = 0; out_frame[i++] = RESP_CODE_STATS_PACKETS; - int json_len = strlen(json_reply); - if (i + json_len <= MAX_FRAME_SIZE) { - memcpy(&out_frame[i], json_reply, json_len); - i += json_len; - _serial->writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + uint32_t recv = radio_driver.getPacketsRecv(); + uint32_t sent = radio_driver.getPacketsSent(); + uint32_t n_sent_flood = getNumSentFlood(); + uint32_t n_sent_direct = getNumSentDirect(); + uint32_t n_recv_flood = getNumRecvFlood(); + uint32_t n_recv_direct = getNumRecvDirect(); + memcpy(&out_frame[i], &recv, 4); i += 4; + memcpy(&out_frame[i], &sent, 4); i += 4; + memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; + memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; + memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; + memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { From 39f83efbfe9c34129996f35eec6de916b890762c Mon Sep 17 00:00:00 2001 From: agessaman Date: Sun, 9 Nov 2025 11:39:47 -0800 Subject: [PATCH 10/53] Remove unused statistics formatting methods and associated header includes from MyMesh class. Whoops. --- examples/companion_radio/MyMesh.cpp | 14 -------------- examples/companion_radio/MyMesh.h | 6 ------ 2 files changed, 20 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6fc9a433..140f58c6 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1612,20 +1612,6 @@ void MyMesh::enterCLIRescue() { Serial.println("========= CLI Rescue ========="); } -void MyMesh::formatStatsReply(char *reply) { - // Use StatsFormatHelper - StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); -} - -void MyMesh::formatRadioStatsReply(char *reply) { - StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); -} - -void MyMesh::formatPacketStatsReply(char *reply) { - StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), - getNumRecvFlood(), getNumRecvDirect()); -} - void MyMesh::checkCLIRescueCmd() { int len = strlen(cli_command); while (Serial.available() && len < sizeof(cli_command)-1) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 61a25e14..f2b56e5e 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -69,7 +69,6 @@ #include #include -#include /* -------------------------------------------------------------------------------------- */ @@ -171,11 +170,6 @@ private: void checkCLIRescueCmd(); void checkSerialInterface(); - // Stats methods - void formatStatsReply(char *reply); - void formatRadioStatsReply(char *reply); - void formatPacketStatsReply(char *reply); - // helpers, short-cuts void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } From b0ce00652f45057493eb9893ec549e1bfaa5fbca Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:00:27 +0100 Subject: [PATCH 11/53] Fix RAK4631 GPS UART pin macros --- variants/rak4631/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 9a0504dc..7db67abf 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -9,8 +9,8 @@ build_flags = ${nrf52_base.build_flags} -D RAK_BOARD -D PIN_BOARD_SCL=14 -D PIN_BOARD_SDA=13 - -D PIN_GPS_TX=16 - -D PIN_GPS_RX=15 + -D PIN_GPS_TX=PIN_SERIAL1_RX + -D PIN_GPS_RX=PIN_SERIAL1_TX -D PIN_GPS_EN=-1 -D PIN_OLED_RESET=-1 -D RADIO_CLASS=CustomSX1262 From 16c294ce6029a0a924762bf8825f8543e1703834 Mon Sep 17 00:00:00 2001 From: Stephan Rodemeier Date: Thu, 13 Nov 2025 22:25:55 +0100 Subject: [PATCH 12/53] Allow SF smaller than 7 to be saved --- examples/companion_radio/MyMesh.cpp | 2 +- src/helpers/CommonCLI.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 598d535f..c0075a22 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1128,7 +1128,7 @@ void MyMesh::handleCmdFrame(size_t len) { uint8_t sf = cmd_frame[i++]; uint8_t cr = cmd_frame[i++]; - if (freq >= 300000 && freq <= 2500000 && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && + if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && bw <= 500000) { _prefs.sf = sf; _prefs.cr = cr; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index fc9fd5eb..93773cce 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -233,7 +233,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { + if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { _callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins); sprintf(reply, "OK - temp params for %d mins", temp_timeout_mins); } else { @@ -411,7 +411,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch float bw = num > 1 ? atof(parts[1]) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { + if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { _prefs->sf = sf; _prefs->cr = cr; _prefs->freq = freq; From 850d57a8f243dce0193008ee46b2f276d4fa5919 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 14 Nov 2025 21:51:28 -0700 Subject: [PATCH 13/53] Refactor float conversion in CommonCLI to use strtof for improved precision and add ftoa3 function for formatting floats with three decimal places in TxtDataHelpers to fix display issue in app and repeater config ui in web REPO: 1. Flash a repeater 2. Connect over lora 3. Set bw to 42.7 KHZ It will revert back due to converting a double to a float. REPO2: 1.Flash Repeater 2. Apply float fix 3. Set to say 20.8 4. try to get value via app or web cli repeater config It wil show blank because it doesnt return a good value. It would be something like 20.7999992 which the app and web apps dont like so the ftoa3 rounds it and returns a 3 decimal point float --- src/helpers/CommonCLI.cpp | 10 +++++----- src/helpers/TxtDataHelpers.cpp | 13 +++++++++++++ src/helpers/TxtDataHelpers.h | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 93773cce..481d9ca7 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -228,8 +228,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(tmp, &command[10]); const char *parts[5]; int num = mesh::Utils::parseTextParts(tmp, parts, 5); - float freq = num > 0 ? atof(parts[0]) : 0.0f; - float bw = num > 1 ? atof(parts[1]) : 0.0f; + float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; + float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0; @@ -284,7 +284,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "radio", 5) == 0) { char freq[16], bw[16]; strcpy(freq, StrHelper::ftoa(_prefs->freq)); - strcpy(bw, StrHelper::ftoa(_prefs->bw)); + strcpy(bw, StrHelper::ftoa3(_prefs->bw)); sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr); } else if (memcmp(config, "rxdelay", 7) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base)); @@ -407,8 +407,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(tmp, &config[6]); const char *parts[4]; int num = mesh::Utils::parseTextParts(tmp, parts, 4); - float freq = num > 0 ? atof(parts[0]) : 0.0f; - float bw = num > 1 ? atof(parts[1]) : 0.0f; + float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; + float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index 09e86c67..d327931f 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -140,6 +140,19 @@ const char* StrHelper::ftoa(float f) { return tmp; } +const char* StrHelper::ftoa3(float f) { + static char s[16]; + int v = (int)(f * 1000.0f + (f >= 0 ? 0.5f : -0.5f)); // rounded ×1000 + int w = v / 1000; // whole + int d = abs(v % 1000); // decimals + snprintf(s, sizeof(s), "%d.%03d", w, d); + for (int i = strlen(s) - 1; i > 0 && s[i] == '0'; i--) + s[i] = 0; + int L = strlen(s); + if (s[L - 1] == '.') s[L - 1] = 0; + return s; +} + uint32_t StrHelper::fromHex(const char* src) { uint32_t n = 0; while (*src) { diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 387e09b9..6ab84d39 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -12,6 +12,7 @@ public: static void strncpy(char* dest, const char* src, size_t buf_sz); static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls static const char* ftoa(float f); + static const char* ftoa3(float f); //Converts float to string with 3 decimal places static bool isBlank(const char* str); static uint32_t fromHex(const char* src); }; From 2058af8453540ff864e414df774509c443f4971d Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 16 Nov 2025 15:55:03 +1100 Subject: [PATCH 14/53] initial support: Keepteen LT1 --- boards/keepteen_lt1.json | 79 ++++++++++++ variants/keepteen_lt1/KeepteenLT1Board.cpp | 75 ++++++++++++ variants/keepteen_lt1/KeepteenLT1Board.h | 51 ++++++++ variants/keepteen_lt1/platformio.ini | 132 +++++++++++++++++++++ variants/keepteen_lt1/target.cpp | 51 ++++++++ variants/keepteen_lt1/target.h | 30 +++++ variants/keepteen_lt1/variant.cpp | 22 ++++ variants/keepteen_lt1/variant.h | 74 ++++++++++++ 8 files changed, 514 insertions(+) create mode 100644 boards/keepteen_lt1.json create mode 100644 variants/keepteen_lt1/KeepteenLT1Board.cpp create mode 100644 variants/keepteen_lt1/KeepteenLT1Board.h create mode 100644 variants/keepteen_lt1/platformio.ini create mode 100644 variants/keepteen_lt1/target.cpp create mode 100644 variants/keepteen_lt1/target.h create mode 100644 variants/keepteen_lt1/variant.cpp create mode 100644 variants/keepteen_lt1/variant.h diff --git a/boards/keepteen_lt1.json b/boards/keepteen_lt1.json new file mode 100644 index 00000000..c23b0b88 --- /dev/null +++ b/boards/keepteen_lt1.json @@ -0,0 +1,79 @@ +{ + "build": { + "arduino":{ + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x00B3" + ], + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "Keepteen LT1", + "mcu": "nrf52840", + "variant": "Keepteen LT1", + "variants_dir": "variants", + "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", + "zephyr" + ], + "name": "Keepteen LT1", + "upload": { + "maximum_ram_size": 248832, + "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": "http://www.keepteen.com/", + "vendor": "Keepteen" + } \ No newline at end of file diff --git a/variants/keepteen_lt1/KeepteenLT1Board.cpp b/variants/keepteen_lt1/KeepteenLT1Board.cpp new file mode 100644 index 00000000..46bff1fc --- /dev/null +++ b/variants/keepteen_lt1/KeepteenLT1Board.cpp @@ -0,0 +1,75 @@ +#include +#include "KeepteenLT1Board.h" + +#include +#include + +static BLEDfu bledfu; + +void KeepteenLT1Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + btn_prev_state = HIGH; + + pinMode(PIN_VBAT_READ, INPUT); + + #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); + #endif + + Wire.begin(); +} + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +bool KeepteenLT1Board::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("KeepteenLT1_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h new file mode 100644 index 00000000..9892638b --- /dev/null +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +class KeepteenLT1Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + uint8_t btn_prev_state; + +public: + void begin(); + + uint8_t getStartupReason() const override { return startup_reason; } + + #define BATTERY_SAMPLES 8 + + uint16_t getBattMilliVolts() override { + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + return (ADC_MULTIPLIER * raw); + } + + const char* getManufacturerName() const override { + return "Keepteen LT1"; + } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } +#endif + + void reboot() override { + NVIC_SystemReset(); + } + + void powerOff() override { + sd_power_system_off(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini new file mode 100644 index 00000000..96a55eb7 --- /dev/null +++ b/variants/keepteen_lt1/platformio.ini @@ -0,0 +1,132 @@ +[KeepteenLT1] +extends = nrf52_base +board = keepteen_lt1 +build_flags = ${nrf52_base.build_flags} + -I variants/keepteen_lt1 + -D KEEPTEEN_LT1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=34 + -D PIN_BOARD_SCL=36 + -D ENV_INCLUDE_GPS=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/keepteen_lt1> +lib_deps= ${nrf52_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + stevemarple/MicroNMEA @ ^2.0.6 + +[env:KeepteenLT1_repeater] +extends = KeepteenLT1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_repeater> + + + + +build_flags = + ${KeepteenLT1.build_flags} + -D ADVERT_NAME='"KeepteenLT1 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_room_server] +extends = KeepteenLT1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_room_server> + + + + +build_flags = ${KeepteenLT1.build_flags} + -D ADVERT_NAME='"KeepteenLT1 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_terminal_chat] +extends = KeepteenLT1 +build_flags = ${KeepteenLT1.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_companion_radio_usb] +extends = KeepteenLT1 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = ${KeepteenLT1.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 + +[env:KeepteenLT1_companion_radio_ble] +extends = KeepteenLT1 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = ${KeepteenLT1.build_flags} + -I examples/companion_radio/ui-new + -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 DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 + +; [env:KeepteenLT1_sensor] +; extends = KeepteenLT1 +; build_flags = +; ${KeepteenLT1.build_flags} +; -D ADVERT_NAME='"KeepteenLT1 Sensor"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D DISPLAY_CLASS=SSD1306Display +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${KeepteenLT1.build_src_filter} +; + +; + +; +<../examples/simple_sensor> +; lib_deps = +; ${KeepteenLT1.lib_deps} diff --git a/variants/keepteen_lt1/target.cpp b/variants/keepteen_lt1/target.cpp new file mode 100644 index 00000000..e72abf08 --- /dev/null +++ b/variants/keepteen_lt1/target.cpp @@ -0,0 +1,51 @@ +#include +#include "target.h" +#include + +KeepteenLT1Board 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); +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); +#endif + +bool radio_init() { + 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); // create new random identity +} + diff --git a/variants/keepteen_lt1/target.h b/variants/keepteen_lt1/target.h new file mode 100644 index 00000000..0f1aa756 --- /dev/null +++ b/variants/keepteen_lt1/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +#include + +extern KeepteenLT1Board 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(); +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(); diff --git a/variants/keepteen_lt1/variant.cpp b/variants/keepteen_lt1/variant.cpp new file mode 100644 index 00000000..47a20f13 --- /dev/null +++ b/variants/keepteen_lt1/variant.cpp @@ -0,0 +1,22 @@ +#include "variant.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() +{ + // set LED pin as output and set it low for off + pinMode(PIN_LED, OUTPUT); + digitalWrite(PIN_LED, LOW); + + // set INPUT_PULLUP for user button + pinMode(PIN_USER_BTN, INPUT_PULLUP); + +} + diff --git a/variants/keepteen_lt1/variant.h b/variants/keepteen_lt1/variant.h new file mode 100644 index 00000000..a2b63fad --- /dev/null +++ b/variants/keepteen_lt1/variant.h @@ -0,0 +1,74 @@ +#ifndef _KEEPTEEN_LT1_H_ +#define _KEEPTEEN_LT1_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) +#define USE_LFRC + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (15) // Blue LED +#define PIN_LED2 (13) // Maybe red power LED? +#define LED_BLUE (-1) // Disable annoying flashing caused by Bluefruit +#define LED_BUILTIN PIN_LED +#define P_LORA_TX_LED PIN_LED +#define LED_STATE_ON 1 + +// Buttons +#define PIN_BUTTON1 (32) // Menu / User Button +#define PIN_USER_BTN PIN_BUTTON1 + +// Analog pins +#define PIN_VBAT_READ (31) +#define AREF_VOLTAGE (3.6F) +#define ADC_MULTIPLIER (1.535F) +#define ADC_RESOLUTION (12) + +// Serial interfaces +#define PIN_SERIAL1_RX (22) +#define PIN_SERIAL1_TX (20) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (2) +#define PIN_SPI_MOSI (38) +#define PIN_SPI_SCK (43) + +// Lora Pins +#define P_LORA_BUSY (29) +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI +#define P_LORA_NSS (45) +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_DIO_1 (10) +#define P_LORA_RESET (9) +#define SX126X_RXEN RADIOLIB_NC +#define SX126X_TXEN RADIOLIB_NC +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (34) +#define PIN_WIRE_SCL (36) +#define I2C_NO_RESCAN + +// GPS L76KB +#define GPS_BAUDRATE 9600 +#define PIN_GPS_TX PIN_SERIAL1_RX +#define PIN_GPS_RX PIN_SERIAL1_TX +#define PIN_GPS_EN (24) + +#endif // _KEEPTEEN_LT1_H_ \ No newline at end of file From bc2256f232c476ada4d4a1bee3863c25ce05f02f Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 16 Nov 2025 16:17:11 +1100 Subject: [PATCH 15/53] Keepteen LT1: remove terminal_chat and sensor targets --- variants/keepteen_lt1/platformio.ini | 33 +--------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini index 96a55eb7..cb3ea9c8 100644 --- a/variants/keepteen_lt1/platformio.ini +++ b/variants/keepteen_lt1/platformio.ini @@ -56,19 +56,6 @@ build_flags = ${KeepteenLT1.build_flags} lib_deps = ${KeepteenLT1.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:KeepteenLT1_terminal_chat] -extends = KeepteenLT1 -build_flags = ${KeepteenLT1.build_flags} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${KeepteenLT1.build_src_filter} - +<../examples/simple_secure_chat/main.cpp> -lib_deps = ${KeepteenLT1.lib_deps} - densaugeo/base64 @ ~1.4.0 - adafruit/RTClib @ ^2.1.3 - [env:KeepteenLT1_companion_radio_usb] extends = KeepteenLT1 board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld @@ -111,22 +98,4 @@ build_src_filter = ${KeepteenLT1.build_src_filter} +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${KeepteenLT1.lib_deps} adafruit/RTClib @ ^2.1.3 - densaugeo/base64 @ ~1.4.0 - -; [env:KeepteenLT1_sensor] -; extends = KeepteenLT1 -; build_flags = -; ${KeepteenLT1.build_flags} -; -D ADVERT_NAME='"KeepteenLT1 Sensor"' -; -D ADVERT_LAT=0.0 -; -D ADVERT_LON=0.0 -; -D ADMIN_PASSWORD='"password"' -; -D DISPLAY_CLASS=SSD1306Display -; ; -D MESH_PACKET_LOGGING=1 -; ; -D MESH_DEBUG=1 -; build_src_filter = ${KeepteenLT1.build_src_filter} -; + -; + -; +<../examples/simple_sensor> -; lib_deps = -; ${KeepteenLT1.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file From 3dd6dc02ea49eef565b58c34c21399e9dcc9beae Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 16 Nov 2025 16:55:16 +0100 Subject: [PATCH 16/53] xiao_s3: use environment sensor manager and add sensor role --- variants/xiao_s3_wio/platformio.ini | 24 ++++++++++++++++++++++++ variants/xiao_s3_wio/target.cpp | 2 +- variants/xiao_s3_wio/target.h | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 95a54012..04a14db2 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -4,7 +4,9 @@ board = seeed_xiao_esp32s3 board_check = true board_build.mcu = esp32s3 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} -I variants/xiao_s3_wio + -UENV_INCLUDE_GPS -D SEEED_XIAO_S3 -D P_LORA_DIO_1=39 -D P_LORA_NSS=41 @@ -15,6 +17,8 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MOSI=9 -D PIN_USER_BTN=21 -D PIN_STATUS_LED=48 + -D PIN_BOARD_SDA=D4 + -D PIN_BOARD_SCL=D5 -D SX126X_RXEN=38 -D SX126X_TXEN=RADIOLIB_NC -D SX126X_DIO2_AS_RF_SWITCH=true @@ -26,6 +30,10 @@ build_flags = ${esp32_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_s3_wio> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} [env:Xiao_S3_WIO_repeater] extends = Xiao_S3_WIO @@ -185,3 +193,19 @@ lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:Xiao_S3_WIO_sensor] +extends = Xiao_S3_WIO +build_src_filter = ${Xiao_S3_WIO.build_src_filter} + +<../examples/simple_sensor> +build_flags = + ${Xiao_S3_WIO.build_flags} + -D ADVERT_NAME='"XiaoS3 sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_S3_WIO.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 41f25da6..26cd27ac 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -14,7 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index b84ab42b..c3227368 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #ifdef DISPLAY_CLASS #include #include @@ -16,7 +16,7 @@ extern XiaoS3WIOBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 838e83b3b54790e91bf180ccdae669b13092ea59 Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 16 Nov 2025 17:07:33 +0100 Subject: [PATCH 17/53] xiao_s3: relocate serial pins on repeater_bridge_rs232 * conflicts with i2c pins that are documented on the same pins * this is a commented target --- variants/xiao_s3_wio/platformio.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 04a14db2..8979edc2 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -65,8 +65,9 @@ lib_deps = ; -D ADMIN_PASSWORD='"password"' ; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 -; -D WITH_RS232_BRIDGE_RX=5 -; -D WITH_RS232_BRIDGE_TX=6 +; RS232 bridge Pins have been relocated from 5,6 which is the i2c bus on xiao_s3 +; -D WITH_RS232_BRIDGE_RX=3 +; -D WITH_RS232_BRIDGE_TX=4 ; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 From a3c9a07377d1895f78c776049ebd2bd99b90f4ae Mon Sep 17 00:00:00 2001 From: agessaman Date: Mon, 17 Nov 2025 09:57:36 -0800 Subject: [PATCH 18/53] Modify CMD_GET_STATS with sub-types for core, radio, and packet statistics. Consolidated to a single RESP_CODE_STATS with a second byte to identify response structure. Updated documentation and examples to reflect the new command structure and response parsing. --- docs/stats_binary_frames.md | 201 ++++++++++++++++++++-------- examples/companion_radio/MyMesh.cpp | 103 +++++++------- 2 files changed, 200 insertions(+), 104 deletions(-) diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md index d4327ea6..1b409912 100644 --- a/docs/stats_binary_frames.md +++ b/docs/stats_binary_frames.md @@ -6,37 +6,53 @@ Binary frame structures for companion radio stats commands. All multi-byte integ | Command | Code | Description | |---------|------|-------------| -| `CMD_GET_STATS_CORE` | 56 | Get core device statistics | -| `CMD_GET_STATS_RADIO` | 57 | Get radio statistics | -| `CMD_GET_STATS_PACKETS` | 58 | Get packet statistics | +| `CMD_GET_STATS` | 56 | Get statistics (2-byte command: code + sub-type) | + +### Stats Sub-Types + +The `CMD_GET_STATS` command uses a 2-byte frame structure: +- **Byte 0:** `CMD_GET_STATS` (56) +- **Byte 1:** Stats sub-type: + - `STATS_TYPE_CORE` (0) - Get core device statistics + - `STATS_TYPE_RADIO` (1) - Get radio statistics + - `STATS_TYPE_PACKETS` (2) - Get packet statistics ## Response Codes | Response | Code | Description | |----------|------|-------------| -| `RESP_CODE_STATS_CORE` | 24 | Core stats response | -| `RESP_CODE_STATS_RADIO` | 25 | Radio stats response | -| `RESP_CODE_STATS_PACKETS` | 26 | Packet stats response | +| `RESP_CODE_STATS` | 24 | Statistics response (2-byte response: code + sub-type) | + +### Stats Response Sub-Types + +The `RESP_CODE_STATS` response uses a 2-byte header structure: +- **Byte 0:** `RESP_CODE_STATS` (24) +- **Byte 1:** Stats sub-type (matches command sub-type): + - `STATS_TYPE_CORE` (0) - Core device statistics response + - `STATS_TYPE_RADIO` (1) - Radio statistics response + - `STATS_TYPE_PACKETS` (2) - Packet statistics response --- -## RESP_CODE_STATS_CORE (24) +## RESP_CODE_STATS + STATS_TYPE_CORE (24, 0) -**Total Frame Size:** 10 bytes +**Total Frame Size:** 11 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| | 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | -| 1 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | -| 3 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | -| 7 | 2 | uint16_t | errors | Error flags bitmask | - | -| 9 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | +| 1 | 1 | uint8_t | stats_type | Always `0x00` (STATS_TYPE_CORE) | - | +| 2 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | +| 4 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | +| 8 | 2 | uint16_t | errors | Error flags bitmask | - | +| 10 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | ### Example Structure (C/C++) ```c struct StatsCore { uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x00 (STATS_TYPE_CORE) uint16_t battery_mv; uint32_t uptime_secs; uint16_t errors; @@ -46,24 +62,26 @@ struct StatsCore { --- -## RESP_CODE_STATS_RADIO (25) +## RESP_CODE_STATS + STATS_TYPE_RADIO (24, 1) -**Total Frame Size:** 13 bytes +**Total Frame Size:** 14 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| -| 0 | 1 | uint8_t | response_code | Always `0x19` (25) | - | -| 1 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | -| 3 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | -| 4 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | -| 5 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | -| 9 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 1 | uint8_t | stats_type | Always `0x01` (STATS_TYPE_RADIO) | - | +| 2 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | +| 4 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | +| 5 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | +| 6 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | +| 10 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | ### Example Structure (C/C++) ```c struct StatsRadio { - uint8_t response_code; // 0x19 + uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x01 (STATS_TYPE_RADIO) int16_t noise_floor; int8_t last_rssi; int8_t last_snr; // Divide by 4.0 to get actual SNR in dB @@ -74,19 +92,20 @@ struct StatsRadio { --- -## RESP_CODE_STATS_PACKETS (26) +## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2) -**Total Frame Size:** 25 bytes +**Total Frame Size:** 26 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| -| 0 | 1 | uint8_t | response_code | Always `0x1A` (26) | - | -| 1 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | -| 5 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | -| 9 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | -| 13 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | -| 17 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | -| 21 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 1 | uint8_t | stats_type | Always `0x02` (STATS_TYPE_PACKETS) | - | +| 2 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | +| 6 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | +| 10 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | +| 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | +| 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | +| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | ### Notes @@ -98,7 +117,8 @@ struct StatsRadio { ```c struct StatsPackets { - uint8_t response_code; // 0x1A + uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x02 (STATS_TYPE_PACKETS) uint32_t recv; uint32_t sent; uint32_t flood_tx; @@ -110,15 +130,38 @@ struct StatsPackets { --- -## Usage Example (Python) +## Command Usage Example (Python) + +```python +# Send CMD_GET_STATS command +def send_get_stats_core(serial_interface): + """Send command to get core stats""" + cmd = bytes([56, 0]) # CMD_GET_STATS (56) + STATS_TYPE_CORE (0) + serial_interface.write(cmd) + +def send_get_stats_radio(serial_interface): + """Send command to get radio stats""" + cmd = bytes([56, 1]) # CMD_GET_STATS (56) + STATS_TYPE_RADIO (1) + serial_interface.write(cmd) + +def send_get_stats_packets(serial_interface): + """Send command to get packet stats""" + cmd = bytes([56, 2]) # CMD_GET_STATS (56) + STATS_TYPE_PACKETS (2) + serial_interface.write(cmd) +``` + +--- + +## Response Parsing Example (Python) ```python import struct def parse_stats_core(frame): - """Parse RESP_CODE_STATS_CORE frame (10 bytes)""" - response_code, battery_mv, uptime_secs, errors, queue_len = \ - struct.unpack('getMillis() / 1000; - uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); - memcpy(&out_frame[i], &battery_mv, 2); i += 2; - memcpy(&out_frame[i], &uptime_secs, 4); i += 4; - memcpy(&out_frame[i], &_err_flags, 2); i += 2; - out_frame[i++] = queue_len; - _serial->writeFrame(out_frame, i); - } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { - int i = 0; - out_frame[i++] = RESP_CODE_STATS_RADIO; - int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); - int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); - int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision - uint32_t tx_air_secs = getTotalAirTime() / 1000; - uint32_t rx_air_secs = getReceiveAirTime() / 1000; - memcpy(&out_frame[i], &noise_floor, 2); i += 2; - out_frame[i++] = last_rssi; - out_frame[i++] = last_snr; - memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; - memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; - _serial->writeFrame(out_frame, i); - } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { - int i = 0; - out_frame[i++] = RESP_CODE_STATS_PACKETS; - uint32_t recv = radio_driver.getPacketsRecv(); - uint32_t sent = radio_driver.getPacketsSent(); - uint32_t n_sent_flood = getNumSentFlood(); - uint32_t n_sent_direct = getNumSentDirect(); - uint32_t n_recv_flood = getNumRecvFlood(); - uint32_t n_recv_direct = getNumRecvDirect(); - memcpy(&out_frame[i], &recv, 4); i += 4; - memcpy(&out_frame[i], &sent, 4); i += 4; - memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; - memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; - memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; - memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; - _serial->writeFrame(out_frame, i); + } else if (cmd_frame[0] == CMD_GET_STATS && len >= 2) { + uint8_t stats_type = cmd_frame[1]; + if (stats_type == STATS_TYPE_CORE) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_CORE; + uint16_t battery_mv = board.getBattMilliVolts(); + uint32_t uptime_secs = _ms->getMillis() / 1000; + uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); + memcpy(&out_frame[i], &battery_mv, 2); i += 2; + memcpy(&out_frame[i], &uptime_secs, 4); i += 4; + memcpy(&out_frame[i], &_err_flags, 2); i += 2; + out_frame[i++] = queue_len; + _serial->writeFrame(out_frame, i); + } else if (stats_type == STATS_TYPE_RADIO) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_RADIO; + int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); + int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); + int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision + uint32_t tx_air_secs = getTotalAirTime() / 1000; + uint32_t rx_air_secs = getReceiveAirTime() / 1000; + memcpy(&out_frame[i], &noise_floor, 2); i += 2; + out_frame[i++] = last_rssi; + out_frame[i++] = last_snr; + memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; + memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; + _serial->writeFrame(out_frame, i); + } else if (stats_type == STATS_TYPE_PACKETS) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_PACKETS; + uint32_t recv = radio_driver.getPacketsRecv(); + uint32_t sent = radio_driver.getPacketsSent(); + uint32_t n_sent_flood = getNumSentFlood(); + uint32_t n_sent_direct = getNumSentDirect(); + uint32_t n_recv_flood = getNumRecvFlood(); + uint32_t n_recv_direct = getNumRecvDirect(); + memcpy(&out_frame[i], &recv, 4); i += 4; + memcpy(&out_frame[i], &sent, 4); i += 4; + memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; + memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; + memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; + memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type + } } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { From 88a614194324bc49b338beee61b96a16b0f5b1b3 Mon Sep 17 00:00:00 2001 From: recrof Date: Tue, 18 Nov 2025 15:36:25 +0100 Subject: [PATCH 19/53] fix: move bme680 detection before bme280 --- .../sensors/EnvironmentSensorManager.cpp | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 98339105..79dc87e5 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -178,6 +178,16 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_BME680 + if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) { + MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); + BME680_initialized = true; + } else { + BME680_initialized = false; + MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); + } + #endif + #if ENV_INCLUDE_BME280 if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); @@ -301,16 +311,6 @@ bool EnvironmentSensorManager::begin() { } #endif - #if ENV_INCLUDE_BME680 - if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) { - MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); - BME680_initialized = true; - } else { - BME680_initialized = false; - MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); - } - #endif - #if ENV_INCLUDE_BMP085 // First argument is MODE (aka oversampling) // choose ULTRALOWPOWER @@ -344,6 +344,19 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_BME680 + if (BME680_initialized) { + if (BME680.performReading()) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); + telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); + next_available_channel++; + } + } + #endif + #if ENV_INCLUDE_BME280 if (BME280_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); @@ -452,19 +465,6 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif - #if ENV_INCLUDE_BME680 - if (BME680_initialized) { - if (BME680.performReading()) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); - telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); - telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); - next_available_channel++; - } - } - #endif - #if ENV_INCLUDE_BMP085 if (BMP085_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature()); @@ -563,7 +563,7 @@ void EnvironmentSensorManager::initBasicGPS() { gps_active = false; //Set GPS visibility off until setting is changed } -// gps code for rak might be moved to MicroNMEALoactionProvider +// gps code for rak might be moved to MicroNMEALoactionProvider // or make a new location provider ... #ifdef RAK_WISBLOCK_GPS void EnvironmentSensorManager::rakGPSInit(){ From 310618e6899337b37fb3a9268f8e6571bf1f760c Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 19 Nov 2025 11:43:52 +0800 Subject: [PATCH 20/53] add heltec_v4 tft expansion box --- src/helpers/ui/SSD1306Display.cpp | 14 +- src/helpers/ui/SSD1306Display.h | 9 +- src/helpers/ui/ST7789LCDDisplay.cpp | 5 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4/HeltecV4Board.cpp | 6 +- variants/heltec_v4/platformio.ini | 265 +++++++++++++++++++++++---- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 6 +- 8 files changed, 267 insertions(+), 44 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index c9da0cf8..4e7fd10a 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,6 +7,10 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } #ifdef DISPLAY_ROTATION display.setRotation(DISPLAY_ROTATION); #endif @@ -15,12 +19,18 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { display.ssd1306_command(SSD1306_DISPLAYON); - _isOn = true; + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); - _isOn = false; + if (_isOn) { + if (_peripher_power) _peripher_power->release(); + _isOn = false; + } } void SSD1306Display::clear() { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index 1a3a9602..d843da85 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -5,6 +5,7 @@ #include #define SSD1306_NO_SPLASH #include +#include #ifndef PIN_OLED_RESET #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) @@ -18,10 +19,16 @@ class SSD1306Display : public DisplayDriver { Adafruit_SSD1306 display; bool _isOn; uint8_t _color; + RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(128, 64, &Wire, PIN_OLED_RESET), + _peripher_power(peripher_power) + { + _isOn = false; + } bool begin(); bool isOn() override { return _isOn; } diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 87f9b8ad..a686c0c8 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -25,10 +25,13 @@ bool ST7789LCDDisplay::begin() { pinMode(PIN_TFT_LEDA_CTL, OUTPUT); digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + pinMode(PIN_TFT_RST, OUTPUT); + digitalWrite(PIN_TFT_RST, LOW); + delay(10); digitalWrite(PIN_TFT_RST, HIGH); // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); #endif diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index a8077148..5b960ca1 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include class ST7789LCDDisplay : public DisplayDriver { - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ public: { _isOn = false; } -#elif LILYGO_TDECK +#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index f143db36..92f93437 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -86,5 +86,9 @@ void HeltecV4Board::begin() { } const char* HeltecV4Board::getManufacturerName() const { - return "Heltec V4"; + #ifdef HELTEC_LORA_V4_TFT + return "Heltec V4 TFT"; + #else + return "Heltec V4 OLED"; + #endif } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c26a5bc6..ba759009 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,11 +20,9 @@ build_flags = -D P_LORA_PA_POWER=7 ;power en -D P_LORA_PA_EN=2 -D P_LORA_PA_TX_EN=46 ;enable tx - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VEXT_EN_ACTIVE=LOW -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true @@ -47,10 +45,44 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[env:heltec_v4_repeater] +[heltec_v4_oled] extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_OLED + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D ENV_PIN_SDA=4 + -D ENV_PIN_SCL=3 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = ${Heltec_lora32_v4.lib_deps} + +[heltec_v4_tft] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_TFT + -D PIN_BOARD_SDA=4 + -D PIN_BOARD_SCL=3 + -D DISPLAY_SCALE_X=2.5 + -D DISPLAY_SCALE_Y=3.75 + -D PIN_TFT_RST=18 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=21 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=15 + -D PIN_TFT_DC=16 + -D PIN_TFT_SCL=17 + -D PIN_TFT_SDA=33 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = + ${Heltec_lora32_v4.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:heltec_v4_repeater] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Repeater"' -D ADVERT_LAT=0.0 @@ -59,18 +91,18 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 [env:heltec_v4_repeater_bridge_espnow] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 @@ -81,18 +113,18 @@ build_flags = ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_room_server] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Room"' -D ADVERT_LAT=0.0 @@ -101,50 +133,50 @@ build_flags = -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_room_server> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_terminal_chat] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_usb] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_ble] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -155,20 +187,20 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_wifi] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -178,21 +210,21 @@ build_flags = -D WIFI_PWD='"mypwd"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_sensor] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} - -D ADVERT_NAME='"Heltec v3 Sensor"' + ${heltec_v4_oled.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -201,9 +233,172 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_sensor> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} + ${esp32_ota.lib_deps} + + +[env:heltec_v4_tft_repeater] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec 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 = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + + +[env:heltec_v4_tft_repeater_bridge_espnow] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -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 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_room_server] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_terminal_chat] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_usb] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_ble] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=ST7789LCDDisplay + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_wifi] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_sensor] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + -D DISPLAY_CLASS=ST7789LCDDisplay +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_sensor> +lib_deps = + ${heltec_v4_tft.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 015c3a8e..0d2bd497 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display; + DISPLAY_CLASS display(&(board.periph_power)); MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index a153b2af..00d2adab 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -9,7 +9,11 @@ #include #include #ifdef DISPLAY_CLASS - #include +#ifdef HELTEC_LORA_V4_OLED + #include +#elif defined(HELTEC_LORA_V4_TFT) + #include +#endif #include #endif From ed9655e14e40d9e4f5361631c1ddc0d3a70de999 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 12:48:33 +1100 Subject: [PATCH 21/53] rename faketec to promicro --- variants/promicro/platformio.ini | 64 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index b1c0c4ea..90962d27 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -1,9 +1,9 @@ -[Faketec] +[Promicro] extends = nrf52_base board = promicro_nrf52840 build_flags = ${nrf52_base.build_flags} -I variants/promicro - -D FAKETEC + -D PROMICRO -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -34,14 +34,14 @@ lib_deps= ${nrf52_base.lib_deps} adafruit/Adafruit BMP280 Library@^2.6.8 stevemarple/MicroNMEA @ ^2.0.6 -[env:Faketec_repeater] -extends = Faketec -build_src_filter = ${Faketec.build_src_filter} +[env:ProMicro_repeater] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_repeater> + + build_flags = - ${Faketec.build_flags} + ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -50,16 +50,16 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:Faketec_room_server] -extends = Faketec -build_src_filter = ${Faketec.build_src_filter} +[env:ProMicro_room_server] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_room_server> + + -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -68,47 +68,47 @@ build_flags = ${Faketec.build_flags} -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:Faketec_terminal_chat] -extends = Faketec -build_flags = ${Faketec.build_flags} +[env:ProMicro_terminal_chat] +extends = Promicro +build_flags = ${Promicro.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_secure_chat/main.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 -[env:Faketec_companion_radio_usb] -extends = Faketec +[env:ProMicro_companion_radio_usb] +extends = Promicro board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld board_upload.maximum_size = 712704 -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 -[env:Faketec_companion_radio_ble] -extends = Faketec +[env:ProMicro_companion_radio_ble] +extends = Promicro board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld board_upload.maximum_size = 712704 -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -118,20 +118,20 @@ build_flags = ${Faketec.build_flags} -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 -[env:Faketec_sensor] -extends = Faketec +[env:ProMicro_sensor] +extends = Promicro build_flags = - ${Faketec.build_flags} + ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -139,9 +139,9 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} + + +<../examples/simple_sensor> lib_deps = - ${Faketec.lib_deps} + ${Promicro.lib_deps} From 2bd47de3b9824d99450baa4d1d90c9729c13032f Mon Sep 17 00:00:00 2001 From: zaquaz Date: Thu, 20 Nov 2025 18:55:39 -0800 Subject: [PATCH 22/53] Added buzzer config persistance accross restart --- examples/companion_radio/DataStore.cpp | 7 +++++++ examples/companion_radio/MyMesh.h | 5 +++-- examples/companion_radio/NodePrefs.h | 1 + examples/companion_radio/ui-new/UITask.cpp | 3 +++ examples/companion_radio/ui-orig/UITask.cpp | 3 +++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index eac027b8..058389fe 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -200,6 +200,11 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; + + // Initialize defaults for any missing fields (backward compatibility) + memset(&_prefs, 0, sizeof(_prefs)); + node_lat = 0.0; + node_lon = 0.0; file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 @@ -221,6 +226,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 file.read(pad, 2); // 78 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.close(); } @@ -252,6 +258,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 file.write(pad, 2); // 78 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.close(); } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 927ec65e..9c22532d 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -152,6 +152,9 @@ protected: pending_login = pending_status = pending_telemetry = pending_discovery = pending_req = 0; } +public: + void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } + private: void writeOKFrame(); void writeErrFrame(uint8_t err_code); @@ -171,11 +174,9 @@ private: void checkSerialInterface(); // helpers, short-cuts - void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } void saveContacts() { _store->saveContacts(this); } -private: DataStore* _store; NodePrefs _prefs; uint32_t pending_login; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index bfde7218..13c9f884 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -24,4 +24,5 @@ struct NodePrefs { // persisted to file float rx_delay_base; uint32_t ble_pin; uint8_t advert_loc_policy; + uint8_t buzzer_quiet; }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 086f8259..9213df12 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -532,6 +532,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #ifdef PIN_BUZZER buzzer.begin(); + buzzer.quiet(_node_prefs->buzzer_quiet); #endif #ifdef PIN_VIBRATION @@ -871,6 +872,8 @@ void UITask::toggleBuzzer() { buzzer.quiet(true); showAlert("Buzzer: OFF", 800); } + _node_prefs->buzzer_quiet = buzzer.isQuiet(); + the_mesh.savePrefs(); _next_refresh = 0; // trigger refresh #endif } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d..f7838d68 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -56,6 +56,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #ifdef PIN_BUZZER buzzer.begin(); + buzzer.quiet(_node_prefs->buzzer_quiet); #endif // Initialize digital button if available @@ -394,6 +395,8 @@ void UITask::handleButtonTriplePress() { buzzer.quiet(true); sprintf(_alert, "Buzzer: OFF"); } + _node_prefs->buzzer_quiet = buzzer.isQuiet(); + the_mesh.savePrefs(); _need_refresh = true; #endif } From b33d226c583bc601787454e512cca3f938200de4 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 21 Nov 2025 15:44:31 +1100 Subject: [PATCH 23/53] * proposal for 'Extended Trace' packets. Using 'flags' byte, lower 2 bits, for path hash size. --- examples/companion_radio/MyMesh.cpp | 52 ++++++++++++++++++----------- src/Identity.h | 3 ++ src/Mesh.cpp | 9 ++--- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c0075a22..0afd11de 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -670,6 +670,11 @@ void MyMesh::onRawDataRecv(mesh::Packet *packet) { void MyMesh::onTraceRecv(mesh::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) { + uint8_t path_sz = flags & 0x03; // NEW v1.11+ + if (12 + path_len + (path_len >> path_sz) + 1 > sizeof(out_frame)) { + MESH_DEBUG_PRINTLN("onTraceRecv(), path_len is too long: %d", (uint32_t)path_len); + return; + } int i = 0; out_frame[i++] = PUSH_CODE_TRACE_DATA; out_frame[i++] = 0; // reserved @@ -681,8 +686,9 @@ void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, i += 4; memcpy(&out_frame[i], path_hashes, path_len); i += path_len; - memcpy(&out_frame[i], path_snrs, path_len); - i += path_len; + + memcpy(&out_frame[i], path_snrs, path_len >> path_sz); + i += path_len >> path_sz; out_frame[i++] = (int8_t)(packet->getSNR() * 4); // extra/final SNR (to this node) if (_serial->isConnected()) { @@ -1446,25 +1452,31 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_BAD_STATE); } - } else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PATH_SIZE) { - uint32_t tag, auth; - memcpy(&tag, &cmd_frame[1], 4); - memcpy(&auth, &cmd_frame[5], 4); - auto pkt = createTrace(tag, auth, cmd_frame[9]); - if (pkt) { - uint8_t path_len = len - 10; - sendDirect(pkt, &cmd_frame[10], path_len); - - uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); - uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); - - out_frame[0] = RESP_CODE_SENT; - out_frame[1] = 0; - memcpy(&out_frame[2], &tag, 4); - memcpy(&out_frame[6], &est_timeout, 4); - _serial->writeFrame(out_frame, 10); + } else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PACKET_PAYLOAD-5) { + uint8_t path_len = len - 10; + uint8_t flags = cmd_frame[9]; + uint8_t path_sz = flags & 0x03; // NEW v1.11+ + if ((path_len >> path_sz) > MAX_PATH_SIZE || (path_len % (1 << path_sz)) != 0) { // make sure is multiple of path_sz + writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else { - writeErrFrame(ERR_CODE_TABLE_FULL); + uint32_t tag, auth; + memcpy(&tag, &cmd_frame[1], 4); + memcpy(&auth, &cmd_frame[5], 4); + auto pkt = createTrace(tag, auth, flags); + if (pkt) { + sendDirect(pkt, &cmd_frame[10], path_len); + + uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); + uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); + + out_frame[0] = RESP_CODE_SENT; + out_frame[1] = 0; + memcpy(&out_frame[2], &tag, 4); + memcpy(&out_frame[6], &est_timeout, 4); + _serial->writeFrame(out_frame, 10); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } } } else if (cmd_frame[0] == CMD_SET_DEVICE_PIN && len >= 5) { diff --git a/src/Identity.h b/src/Identity.h index 42fb9d9a..60e8783b 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -23,6 +23,9 @@ public: bool isHashMatch(const uint8_t* hash) const { return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0; } + bool isHashMatch(const uint8_t* hash, uint8_t len) const { + return memcmp(hash, pub_key, len) == 0; + } /** * \brief Performs Ed25519 signature verification. diff --git a/src/Mesh.cpp b/src/Mesh.cpp index f1271574..1bda9e96 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -52,14 +52,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint32_t auth_code; memcpy(&auth_code, &pkt->payload[i], 4); i += 4; uint8_t flags = pkt->payload[i++]; + uint8_t path_sz = flags & 0x03; // NEW v1.11+: lower 2 bits is path hash size uint8_t len = pkt->payload_len - i; - if (pkt->path_len >= len) { // TRACE has reached end of given path + uint8_t offset = 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 + pkt->path_len]) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { + } else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { // append SNR (Not hash!) - pkt->path[pkt->path_len] = (int8_t) (pkt->getSNR()*4); - pkt->path_len += PATH_HASH_SIZE; + pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4); uint32_t d = getDirectRetransmitDelay(pkt); return ACTION_RETRANSMIT_DELAYED(5, d); // schedule with priority 5 (for now), maybe make configurable? From 031fa1e704da52c8d31dba1799fbf19b65ed14ba Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Thu, 20 Nov 2025 21:58:42 -0800 Subject: [PATCH 24/53] Changed uint to a uint8_t --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 4 ++-- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/BaseChatMesh.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4136818c..becbb759 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -541,7 +541,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->isAdmin()) { // a CLI command uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index de06b4c6..78588f0f 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -394,7 +394,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 20d9921b..ca07f496 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -550,7 +550,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from->isAdmin()) { // a CLI command uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (sender_timestamp > from->last_timestamp) { // prevent replay attacks if (flags == TXT_TYPE_PLAIN) { @@ -608,7 +608,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } } -bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) { +bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len) { MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from "); #ifdef MESH_DEBUG mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE); diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 00d9c698..1b0d83b3 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -127,7 +127,7 @@ protected: bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; - virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len); + virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len); void sendAckTo(const ClientInfo& dest, uint32_t ack_hash); private: FILESYSTEM* _fs; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index b4072657..4ab3e03b 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -166,7 +166,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { uint32_t timestamp; memcpy(×tamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = data[4] >> 2; // message attempt number, and other flags + uint8_t flags = data[4] >> 2; // message attempt number, and other flags // len can be > original length, but 'text' will be padded with zeroes data[len] = 0; // need to make a C string again, with null terminator From 454f6b2583496cc4937b111ab4c7d6eb3b79e9e7 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 17:57:49 +1100 Subject: [PATCH 25/53] rename adverts --- variants/promicro/platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 90962d27..78ea5fa1 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -42,7 +42,7 @@ build_src_filter = ${Promicro.build_src_filter} + build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Repeater"' + -D ADVERT_NAME='"ProMicro Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -60,7 +60,7 @@ build_src_filter = ${Promicro.build_src_filter} + + build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Room"' + -D ADVERT_NAME='"ProMicro Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -132,7 +132,7 @@ lib_deps = ${Promicro.lib_deps} extends = Promicro build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Sensor"' + -D ADVERT_NAME='"ProMicro Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' From 5a3ea64a97dc0cc419ee553d0da5c4c7656b7858 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 18:15:30 +1100 Subject: [PATCH 26/53] Repeater: add adc.multiplier setting --- examples/simple_repeater/MyMesh.cpp | 4 ++++ src/MeshCore.h | 2 ++ src/helpers/CommonCLI.cpp | 14 ++++++++++++-- src/helpers/CommonCLI.h | 1 + variants/promicro/PromicroBoard.h | 14 +++++++++++++- 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4136818c..091d7901 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -710,6 +710,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.gps_enabled = 0; _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + + _prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier } void MyMesh::begin(FILESYSTEM *fs) { @@ -733,6 +735,8 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif diff --git a/src/MeshCore.h b/src/MeshCore.h index 94bf351d..d65c2b93 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -42,6 +42,8 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; + virtual void setAdcMultiplier(float multiplier) {}; + virtual float getAdcMultiplier() const { return 1.0f; } virtual const char* getManufacturerName() const = 0; virtual void onBeforeTransmit() { } virtual void onAfterTransmit() { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 93773cce..b33d71aa 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -70,7 +70,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 - // 166 + file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 + // 170 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -83,6 +84,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); + _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f); // sanitise bad bridge pref values _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1); @@ -148,7 +150,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 - // 166 + file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 + // 170 file.close(); } @@ -331,6 +334,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "bridge.secret", 13) == 0) { sprintf(reply, "> %s", _prefs->bridge_secret); #endif + } else if (memcmp(config, "adc.multiplier", 14) == 0) { + sprintf(reply, "> %s", StrHelper::ftoa(_prefs->adc_multiplier)); } else { sprintf(reply, "??: %s", config); } @@ -523,6 +528,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch savePrefs(); strcpy(reply, "OK"); #endif + } else if (memcmp(config, "adc.multiplier ", 15) == 0) { + _prefs->adc_multiplier = atof(&config[15]); + _board->setAdcMultiplier(_prefs->adc_multiplier); + savePrefs(); + strcpy(reply, "OK"); } else { sprintf(reply, "unknown config: %s", config); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index a665e014..068783ab 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -47,6 +47,7 @@ struct NodePrefs { // persisted to file uint32_t gps_interval; // in seconds uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; + float adc_multiplier; }; class CommonCLICallbacks { diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index e4b67415..9dfb7b2f 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -23,6 +23,7 @@ class PromicroBoard : public mesh::MainBoard { protected: uint8_t startup_reason; uint8_t btn_prev_state; + float adc_mult = ADC_MULTIPLIER; public: void begin(); @@ -39,7 +40,18 @@ public: raw += analogRead(PIN_VBAT_READ); } raw = raw / BATTERY_SAMPLES; - return (ADC_MULTIPLIER * raw); + return (adc_mult * raw); + } + + void setAdcMultiplier(float multiplier) override { + if (multiplier == 0.0f) { + adc_mult = ADC_MULTIPLIER;} + else { + adc_mult = multiplier; + } + } + float getAdcMultiplier() const override { + return adc_mult; } const char* getManufacturerName() const override { From e13c064487f11a99eaad173b556e0a4dc92a1350 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 21:46:55 +1100 Subject: [PATCH 27/53] add board.setAdcMultiplier to room server and sensor --- examples/simple_room_server/MyMesh.cpp | 2 ++ examples/simple_sensor/SensorMesh.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index de06b4c6..7b575e6f 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -641,6 +641,8 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 20d9921b..96a3791d 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -740,6 +740,8 @@ void SensorMesh::begin(FILESYSTEM* fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif From fc93d84fb8eac695fe79f602ad0e70d59dd07823 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 23:44:17 +1100 Subject: [PATCH 28/53] tweaks get/set adcMultiplier logic --- src/MeshCore.h | 4 ++-- src/helpers/CommonCLI.cpp | 21 +++++++++++++++++---- variants/promicro/PromicroBoard.h | 9 +++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/MeshCore.h b/src/MeshCore.h index d65c2b93..11a6a5b4 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -42,8 +42,8 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; - virtual void setAdcMultiplier(float multiplier) {}; - virtual float getAdcMultiplier() const { return 1.0f; } + virtual bool setAdcMultiplier(float multiplier) { return false; }; + virtual float getAdcMultiplier() const { return 0.0f; } virtual const char* getManufacturerName() const = 0; virtual void onBeforeTransmit() { } virtual void onAfterTransmit() { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b33d71aa..17b2b753 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -335,7 +335,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", _prefs->bridge_secret); #endif } else if (memcmp(config, "adc.multiplier", 14) == 0) { - sprintf(reply, "> %s", StrHelper::ftoa(_prefs->adc_multiplier)); + float adc_mult = _board->getAdcMultiplier(); + if (adc_mult == 0.0f) { + strcpy(reply, "Error: unsupported by this board"); + } else { + sprintf(reply, "> %.3f", adc_mult); + } } else { sprintf(reply, "??: %s", config); } @@ -530,9 +535,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch #endif } else if (memcmp(config, "adc.multiplier ", 15) == 0) { _prefs->adc_multiplier = atof(&config[15]); - _board->setAdcMultiplier(_prefs->adc_multiplier); - savePrefs(); - strcpy(reply, "OK"); + if (_board->setAdcMultiplier(_prefs->adc_multiplier)) { + savePrefs(); + if (_prefs->adc_multiplier == 0.0f) { + strcpy(reply, "OK - using default board multiplier"); + } else { + sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier); + } + } else { + _prefs->adc_multiplier = 0.0f; + strcpy(reply, "Error: unsupported by this board"); + }; } else { sprintf(reply, "unknown config: %s", config); } diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 9dfb7b2f..dc20e550 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -43,15 +43,20 @@ public: return (adc_mult * raw); } - void setAdcMultiplier(float multiplier) override { + bool setAdcMultiplier(float multiplier) override { if (multiplier == 0.0f) { adc_mult = ADC_MULTIPLIER;} else { adc_mult = multiplier; } + return true; } float getAdcMultiplier() const override { - return adc_mult; + if (adc_mult == 0.0f) { + return ADC_MULTIPLIER; + } else { + return adc_mult; + } } const char* getManufacturerName() const override { From 07e7e2d44bfd68abbe87f73a853b04d76b37ddf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= Date: Sat, 22 Nov 2025 02:06:44 +0100 Subject: [PATCH 29/53] companion: Suspend radio when hibernating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should significantly reduce power consumption in hibernation. Fixes: #1014 Signed-off-by: Jaroslav Škarvada Signed-off-by: Frieder Schrempf # generalize for all radios and UIs --- examples/companion_radio/ui-new/UITask.cpp | 1 + examples/companion_radio/ui-orig/UITask.cpp | 6 ++++-- src/helpers/radiolib/CustomSX1262Wrapper.h | 3 +++ src/helpers/radiolib/RadioLibWrappers.h | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 086f8259..d8d778c3 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -650,6 +650,7 @@ void UITask::shutdown(bool restart){ _board->reboot(); } else { _display->turnOff(); + radio_driver.powerOff(); _board->powerOff(); } } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d..89dda116 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -292,10 +292,12 @@ void UITask::shutdown(bool restart){ #endif // PIN_BUZZER - if (restart) + if (restart) { _board->reboot(); - else + } else { + radio_driver.powerOff(); _board->powerOff(); + } } void UITask::loop() { diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 119f6dce..1afee5e8 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -19,4 +19,7 @@ public: int sf = ((CustomSX1262 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + virtual void powerOff() override { + ((CustomSX1262 *)_radio)->sleep(false); + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 25cc5358..3c26d372 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -21,6 +21,7 @@ public: RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } void begin() override; + virtual void powerOff() { _radio->sleep(); } int recvRaw(uint8_t* bytes, int sz) override; uint32_t getEstAirtimeFor(int len_bytes) override; bool startSendRaw(const uint8_t* bytes, int len) override; From 0f565323a092fbf13354a1d908e9564694c0f113 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 19 Nov 2025 11:40:00 +0100 Subject: [PATCH 30/53] variants: WisMesh Tag: Enable DC/DC regulator According to the documentation and experiments on other boards using NRF52 + SX1262 this reduces the power consumption significantly. This assumes that the hardware actually has the inductor for the internal DC/DC regulator populated which is very likely. Even if not, it won't hurt. Signed-off-by: Frieder Schrempf --- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 68ce2fd8..28f6f713 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -21,6 +21,8 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void RAKWismeshTagBoard::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; + pinMode(PIN_VBAT_READ, INPUT); pinMode(PIN_USER_BTN, INPUT_PULLUP); From b9b82fcf1bfa57ce4978b9e3e466343dc79e156f Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 20 Nov 2025 09:09:33 +0100 Subject: [PATCH 31/53] variants: WisMesh Tag: Enable status LED Use the blue LED as status LED. This prevents the blue LED from being always-on and draining the battery. Instead use it as status LED with blink patterns as other companion devices do. Signed-off-by: Frieder Schrempf --- variants/rak_wismesh_tag/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/rak_wismesh_tag/variant.h b/variants/rak_wismesh_tag/variant.h index b0e51efc..3b8e079f 100644 --- a/variants/rak_wismesh_tag/variant.h +++ b/variants/rak_wismesh_tag/variant.h @@ -66,7 +66,7 @@ #define LED_BLUE (36) #define LED_GREEN (35) -//#define PIN_STATUS_LED LED_BLUE +#define PIN_STATUS_LED LED_BLUE #define LED_BUILTIN LED_GREEN #define LED_PIN LED_GREEN #define LED_STATE_ON HIGH From 11f119a7fb7f868d276d495abd7c8755db234e08 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 19 Nov 2025 12:01:07 +0100 Subject: [PATCH 32/53] variants: XIAO NRF52: Enable DC/DC regulator This reduces the power consumption by approximately 25%. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 03bb674e..a709b1c3 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -23,6 +23,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void XiaoNrf52Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); From c76d337a00e6db7ef110e25366bd9a9b8726f5cb Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 19 Nov 2025 17:25:05 +0100 Subject: [PATCH 33/53] variants: XIAO NRF52: Enable user button The Xiao nRF52840 combined with the Wio-SX1262 is often used for cheap and compact DIY companion nodes. The Wio actually has an onboard pushbutton that can be used as user button. Enable support for the button. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 4 ++++ variants/xiao_nrf52/platformio.ini | 7 +++++++ variants/xiao_nrf52/target.cpp | 4 ++++ variants/xiao_nrf52/target.h | 5 +++++ variants/xiao_nrf52/variant.h | 2 +- 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index a709b1c3..396880ab 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -29,6 +29,10 @@ void XiaoNrf52Board::begin() { pinMode(VBAT_ENABLE, OUTPUT); digitalWrite(VBAT_ENABLE, HIGH); +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT); +#endif + #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); #endif diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 888e9493..edbf6275 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -26,10 +26,13 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_WIRE_SCL=D6 -D PIN_WIRE_SDA=D7 + -D PIN_USER_BTN=PIN_BUTTON1 + -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${nrf52_base.build_src_filter} + + +<../variants/xiao_nrf52> + + debug_tool = jlink upload_protocol = nrfutil lib_deps = ${nrf52_base.lib_deps} @@ -41,6 +44,7 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = ${Xiao_nrf52.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 @@ -52,6 +56,7 @@ build_flags = build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -62,6 +67,7 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = ${Xiao_nrf52.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D QSPIFLASH=1 @@ -70,6 +76,7 @@ build_flags = build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index 41142eb6..c9c02d21 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -2,6 +2,10 @@ #include "target.h" #include +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + XiaoNrf52Board board; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); diff --git a/variants/xiao_nrf52/target.h b/variants/xiao_nrf52/target.h index 86f546b8..e1ea2a6b 100644 --- a/variants/xiao_nrf52/target.h +++ b/variants/xiao_nrf52/target.h @@ -9,6 +9,11 @@ #include #include +#ifdef DISPLAY_CLASS + #include + extern DISPLAY_CLASS display; +#endif + extern XiaoNrf52Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index c54f3c2f..c888a833 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -38,7 +38,7 @@ extern "C" #define LED_STATE_ON (1) // State when LED is litted // Buttons -#define PIN_BUTTON1 (PINS_COUNT) +#define PIN_BUTTON1 (0) // Digital PINs static const uint8_t D0 = 0 ; From 4a8dcb4906ba4452e6fc0e99898bb5cd67d4cad3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 19 Nov 2025 17:26:15 +0100 Subject: [PATCH 34/53] variants: XIAO NRF52: Support power-off via user button Add the necessary code to properly power-off the Xiao + Wio companions. This way we can achieve around 15 microamps of power consumption in the off state. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/XiaoNrf52Board.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index b229507a..f3766012 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -46,6 +46,24 @@ public: NVIC_SystemReset(); } + void powerOff() override { + // set led on and wait for button release before poweroff + digitalWrite(PIN_LED, LOW); +#ifdef PIN_USER_BTN + while(digitalRead(PIN_USER_BTN) == LOW); +#endif + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_BLUE, HIGH); + digitalWrite(PIN_LED, HIGH); + +#ifdef PIN_USER_BTN + // configure button press to wake up when in powered off state + 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(); + } + bool startOTAUpdate(const char* id, char reply[]) override; }; From 048bd268a100bb1aad4dbfaff0a5d51cff242695 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 20 Nov 2025 10:58:14 +0100 Subject: [PATCH 35/53] companion: ui: Respect LED_STATE_ON for status LED The current logic only works for active high LEDs. Some boards need an active low level control and therefore they set LED_STATE_ON to 0. Take this into account and use the correct LED pattern for both cases. Signed-off-by: Frieder Schrempf --- examples/companion_radio/ui-new/UITask.cpp | 2 +- examples/companion_radio/ui-orig/UITask.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index d8d778c3..f12667c7 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -618,7 +618,7 @@ void UITask::userLedHandler() { led_state = 0; next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment; } - digitalWrite(PIN_STATUS_LED, led_state); + digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON); } #endif } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 89dda116..1fef8572 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -269,7 +269,7 @@ void UITask::userLedHandler() { state = 0; next_change = cur_time + LED_CYCLE_MILLIS - last_increment; } - digitalWrite(PIN_STATUS_LED, state); + digitalWrite(PIN_STATUS_LED, state == LED_STATE_ON); } #endif } From 5235516dc7033f69d285eb91a86b49fce3378636 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 20 Nov 2025 10:59:37 +0100 Subject: [PATCH 36/53] variants: XIAO NRF52: Enable status LED Fix the active state of the LEDs (active low) and enable the status LED. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/variant.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index c888a833..3f4d7afe 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -34,8 +34,9 @@ extern "C" #define LED_RED (11) #define LED_GREEN (13) #define LED_BLUE (12) +#define PIN_STATUS_LED (LED_BLUE) -#define LED_STATE_ON (1) // State when LED is litted +#define LED_STATE_ON (0) // State when LED is on // Buttons #define PIN_BUTTON1 (0) From 32d622d96964881463c9f7bbdd5b301862078da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= Date: Sat, 22 Nov 2025 02:06:44 +0100 Subject: [PATCH 37/53] variants: Heltec T114: Disable LED and GPS when powering off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should reduce power consumption in hibernation. Signed-off-by: Jaroslav Škarvada --- variants/heltec_t114/T114Board.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 49d1ec37..0f7fc47f 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -48,6 +48,13 @@ public: } void powerOff() override { + #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(); } From 7723a4cb34e5a8b380e378fb2c2170975dc85efe Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Sat, 22 Nov 2025 10:31:47 +0100 Subject: [PATCH 38/53] variants: Heltec T114: Enable DC/DC regulator According to the documentation and experiments on other boards using NRF52 + SX1262 this reduces the power consumption significantly. Signed-off-by: Frieder Schrempf --- variants/heltec_t114/T114Board.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index f8d170b5..f46a1a84 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -21,6 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void T114Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT_READ, INPUT); From dc58f0ea83ad12653075fef31b9afe4175d1671d Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 24 Nov 2025 22:56:55 +1100 Subject: [PATCH 39/53] * BUG FIX: repeater remote admin, flood login should invalidate the client->out_path --- examples/simple_repeater/MyMesh.cpp | 8 ++++++-- examples/simple_repeater/MyMesh.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 091d7901..a9be6c40 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -82,7 +82,7 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn #endif } -uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { ClientInfo* client = NULL; if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); @@ -123,6 +123,10 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } } + if (is_flood) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; @@ -438,7 +442,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request - reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes // TODO } else { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index d8a20486..98bce787 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -113,7 +113,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); - uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 0e903de72cf6740ab5be070b3fd97dd0e0b3884f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 25 Nov 2025 15:09:51 +1100 Subject: [PATCH 40/53] * BUG FIX: same remote login fix as repeater --- examples/simple_sensor/SensorMesh.cpp | 8 ++++++-- examples/simple_sensor/SensorMesh.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 96a3791d..6116c4de 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -326,7 +326,7 @@ int SensorMesh::getAGCResetInterval() const { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } -uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { ClientInfo* client; if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); @@ -359,6 +359,10 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } + if (is_flood) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; @@ -451,7 +455,7 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con data[len] = 0; // ensure null terminator uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request - reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes // TODO } else { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 00d9c698..627ef549 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -148,7 +148,7 @@ private: uint8_t pending_sf; uint8_t pending_cr; - uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 30ccc1fa0148c7af036cd05a8eecd444f644b83b Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 25 Nov 2025 15:12:48 +1100 Subject: [PATCH 41/53] * BUG FIX: remote login fix same as repeater --- examples/simple_room_server/MyMesh.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 7b575e6f..89505a3b 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -332,6 +332,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } + if (packet->isRouteFlood()) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? From eafbd85d1706698cbc09ca674d49743a9bda4445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 25 Nov 2025 11:53:21 +0000 Subject: [PATCH 42/53] Add RAK4631 support for rs232 bridge --- src/helpers/bridges/RS232Bridge.cpp | 2 ++ variants/rak4631/platformio.ini | 44 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 554e8fcc..77332855 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -15,6 +15,8 @@ void RS232Bridge::begin() { #if defined(ESP32) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); +#elif defined(RAK_4631) + ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(NRF52_PLATFORM) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(RP2040_PLATFORM) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 7db67abf..37800c06 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -45,6 +45,50 @@ build_src_filter = ${rak4631.build_src_filter} + +<../examples/simple_repeater> +[env:RAK_4631_repeater_bridge_rs232_tx0_rx0] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=19 + -D WITH_RS232_BRIDGE_TX=20 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +build_src_filter = ${rak4631.build_src_filter} + + + + + +<../examples/simple_repeater> + +[env:RAK_4631_repeater_bridge_rs232_tx1_rx1] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial1 + -D WITH_RS232_BRIDGE_RX=15 + -D WITH_RS232_BRIDGE_TX=16 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +build_src_filter = ${rak4631.build_src_filter} + + + + + +<../examples/simple_repeater> + [env:RAK_4631_room_server] extends = rak4631 build_flags = From baedddb25dead095d439ce458ed7439f10ffde44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 25 Nov 2025 16:19:28 +0000 Subject: [PATCH 43/53] Rename RS232 bridge environments and update build flags for Serial1 and Serial2 --- variants/rak4631/platformio.ini | 51 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 37800c06..b3357855 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -45,29 +45,7 @@ build_src_filter = ${rak4631.build_src_filter} + +<../examples/simple_repeater> -[env:RAK_4631_repeater_bridge_rs232_tx0_rx0] -extends = rak4631 -build_flags = - ${rak4631.build_flags} - -D DISPLAY_CLASS=SSD1306Display - -D ADVERT_NAME='"RS232 Bridge"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=50 - -D WITH_RS232_BRIDGE=Serial2 - -D WITH_RS232_BRIDGE_RX=19 - -D WITH_RS232_BRIDGE_TX=20 -; -D BRIDGE_DEBUG=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -; -D CORE_DEBUG_LEVEL=3 -build_src_filter = ${rak4631.build_src_filter} - + - + - +<../examples/simple_repeater> - -[env:RAK_4631_repeater_bridge_rs232_tx1_rx1] +[env:RAK_4631_repeater_bridge_rs232_serial1] extends = rak4631 build_flags = ${rak4631.build_flags} @@ -78,8 +56,31 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_RS232_BRIDGE=Serial1 - -D WITH_RS232_BRIDGE_RX=15 - -D WITH_RS232_BRIDGE_TX=16 + -D WITH_RS232_BRIDGE_RX=PIN_SERIAL1_RX + -D WITH_RS232_BRIDGE_TX=PIN_SERIAL1_TX + -UENV_INCLUDE_GPS +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +build_src_filter = ${rak4631.build_src_filter} + + + + + +<../examples/simple_repeater> + +[env:RAK_4631_repeater_bridge_rs232_serial2] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX + -D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 From 5b7d73866cd16d3248fb4598ab0e0185414efdc4 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 25 Nov 2025 19:41:01 +0100 Subject: [PATCH 44/53] fix building issues with heltec wireless paper and heltec tracker --- variants/heltec_tracker/platformio.ini | 8 ++++++++ variants/heltec_tracker/target.h | 2 +- variants/heltec_wireless_paper/platformio.ini | 12 ++++++++++-- variants/heltec_wireless_paper/target.h | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 4f48ac21..797eafdc 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -6,6 +6,14 @@ build_flags = -I variants/heltec_tracker -D HELTEC_LORA_V3 -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial + -D ESP32_CPU_FREQ=80 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 8ac5eb72..23fab16e 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include <../heltec_v3/HeltecV3Board.h> #include #include #include diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index c9ad758b..9cf76153 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -5,12 +5,20 @@ build_flags = ${esp32_base.build_flags} -I variants/heltec_wireless_paper -D HELTEC_WIRELESS_PAPER + -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D P_LORA_TX_LED=18 - ; -D PIN_BOARD_SDA=17 - ; -D PIN_BOARD_SCL=18 + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=45 -D PIN_VBAT_READ=20 diff --git a/variants/heltec_wireless_paper/target.h b/variants/heltec_wireless_paper/target.h index 65b972d0..b89c486f 100644 --- a/variants/heltec_wireless_paper/target.h +++ b/variants/heltec_wireless_paper/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include <../heltec_v3/HeltecV3Board.h> #include #include #include From e98c79ae480190e9452aa45f33de1d4a1aa47fba Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 25 Nov 2025 19:45:51 +0100 Subject: [PATCH 45/53] added missing NonBlockingRTTTL dependency, added USB and WIFI companions --- variants/thinknode_m2/platformio.ini | 41 +++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini index fb691d92..b2ebca73 100644 --- a/variants/thinknode_m2/platformio.ini +++ b/variants/thinknode_m2/platformio.ini @@ -145,10 +145,49 @@ build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> - lib_deps = ${ThinkNode_M2.lib_deps} densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M2_companion_radio_usb] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.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_M2.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M2_companion_radio_wifi] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${ThinkNode_M2.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 [env:ThinkNode_M2_companion_radio_serial] extends = ThinkNode_M2 From 6c7b5390e2d8fdcfacebb358d4740b06d9ad4d29 Mon Sep 17 00:00:00 2001 From: zaquaz Date: Wed, 26 Nov 2025 18:37:56 -0800 Subject: [PATCH 46/53] Remove default setting, since it is handled in MyMesh --- examples/companion_radio/DataStore.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 058389fe..00e25cb2 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -200,11 +200,6 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; - - // Initialize defaults for any missing fields (backward compatibility) - memset(&_prefs, 0, sizeof(_prefs)); - node_lat = 0.0; - node_lon = 0.0; file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 From 3ddfdd477b0402eaa71d1600770d9e50731b1fa7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 27 Nov 2025 21:34:52 +1100 Subject: [PATCH 47/53] Revert "add heltec_v4 tft expansion box" This reverts commit 310618e6899337b37fb3a9268f8e6571bf1f760c. --- src/helpers/ui/SSD1306Display.cpp | 14 +- src/helpers/ui/SSD1306Display.h | 9 +- src/helpers/ui/ST7789LCDDisplay.cpp | 5 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4/HeltecV4Board.cpp | 6 +- variants/heltec_v4/platformio.ini | 267 ++++----------------------- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 6 +- 8 files changed, 45 insertions(+), 268 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 4e7fd10a..c9da0cf8 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,10 +7,6 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { - if (!_isOn) { - if (_peripher_power) _peripher_power->claim(); - _isOn = true; - } #ifdef DISPLAY_ROTATION display.setRotation(DISPLAY_ROTATION); #endif @@ -19,18 +15,12 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { display.ssd1306_command(SSD1306_DISPLAYON); - if (!_isOn) { - if (_peripher_power) _peripher_power->claim(); - _isOn = true; - } + _isOn = true; } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); - if (_isOn) { - if (_peripher_power) _peripher_power->release(); - _isOn = false; - } + _isOn = false; } void SSD1306Display::clear() { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index d843da85..1a3a9602 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -5,7 +5,6 @@ #include #define SSD1306_NO_SPLASH #include -#include #ifndef PIN_OLED_RESET #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) @@ -19,16 +18,10 @@ class SSD1306Display : public DisplayDriver { Adafruit_SSD1306 display; bool _isOn; uint8_t _color; - RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), - display(128, 64, &Wire, PIN_OLED_RESET), - _peripher_power(peripher_power) - { - _isOn = false; - } + SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } bool begin(); bool isOn() override { return _isOn; } diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index a686c0c8..87f9b8ad 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -25,13 +25,10 @@ bool ST7789LCDDisplay::begin() { pinMode(PIN_TFT_LEDA_CTL, OUTPUT); digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - pinMode(PIN_TFT_RST, OUTPUT); - digitalWrite(PIN_TFT_RST, LOW); - delay(10); digitalWrite(PIN_TFT_RST, HIGH); // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) + #ifdef LILYGO_TDECK displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); #endif diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index 5b960ca1..a8077148 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include class ST7789LCDDisplay : public DisplayDriver { - #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) + #ifdef LILYGO_TDECK SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ public: { _isOn = false; } -#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) +#elif LILYGO_TDECK ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 92f93437..f143db36 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -86,9 +86,5 @@ void HeltecV4Board::begin() { } const char* HeltecV4Board::getManufacturerName() const { - #ifdef HELTEC_LORA_V4_TFT - return "Heltec V4 TFT"; - #else - return "Heltec V4 OLED"; - #endif + return "Heltec V4"; } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ba759009..c26a5bc6 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,9 +20,11 @@ build_flags = -D P_LORA_PA_POWER=7 ;power en -D P_LORA_PA_EN=2 -D P_LORA_PA_TX_EN=46 ;enable tx + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=LOW + -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true @@ -45,44 +47,10 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[heltec_v4_oled] -extends = Heltec_lora32_v4 -build_flags = - ${Heltec_lora32_v4.build_flags} - -D HELTEC_LORA_V4_OLED - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 - -D ENV_PIN_SDA=4 - -D ENV_PIN_SCL=3 -build_src_filter= ${Heltec_lora32_v4.build_src_filter} -lib_deps = ${Heltec_lora32_v4.lib_deps} - -[heltec_v4_tft] -extends = Heltec_lora32_v4 -build_flags = - ${Heltec_lora32_v4.build_flags} - -D HELTEC_LORA_V4_TFT - -D PIN_BOARD_SDA=4 - -D PIN_BOARD_SCL=3 - -D DISPLAY_SCALE_X=2.5 - -D DISPLAY_SCALE_Y=3.75 - -D PIN_TFT_RST=18 - -D PIN_TFT_VDD_CTL=-1 - -D PIN_TFT_LEDA_CTL=21 - -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH - -D PIN_TFT_CS=15 - -D PIN_TFT_DC=16 - -D PIN_TFT_SCL=17 - -D PIN_TFT_SDA=33 -build_src_filter= ${Heltec_lora32_v4.build_src_filter} -lib_deps = - ${Heltec_lora32_v4.lib_deps} - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 - [env:heltec_v4_repeater] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Repeater"' -D ADVERT_LAT=0.0 @@ -91,18 +59,18 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/simple_repeater> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 [env:heltec_v4_repeater_bridge_espnow] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 @@ -113,18 +81,18 @@ build_flags = ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + +<../examples/simple_repeater> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_room_server] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Room"' -D ADVERT_LAT=0.0 @@ -133,50 +101,50 @@ build_flags = -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/simple_room_server> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_terminal_chat] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_usb] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_ble] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -187,20 +155,20 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_wifi] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -210,21 +178,21 @@ build_flags = -D WIFI_PWD='"mypwd"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_sensor] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} - -D ADVERT_NAME='"Heltec v4 Sensor"' + ${Heltec_lora32_v4.build_flags} + -D ADVERT_NAME='"Heltec v3 Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -233,172 +201,9 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/simple_sensor> lib_deps = - ${heltec_v4_oled.lib_deps} - ${esp32_ota.lib_deps} - - -[env:heltec_v4_tft_repeater] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -D ADVERT_NAME='"Heltec 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 = ${heltec_v4_tft.build_src_filter} - + - +<../examples/simple_repeater> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - bakercp/CRC32 @ ^2.0.0 - - -[env:heltec_v4_tft_repeater_bridge_espnow] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -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 -; -D BRIDGE_DEBUG=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - + - +<../examples/simple_repeater> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - -[env:heltec_v4_tft_room_server] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -D ADVERT_NAME='"Heltec Room"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D ROOM_PASSWORD='"hello"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - +<../examples/simple_room_server> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - -[env:heltec_v4_tft_terminal_chat] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<../examples/simple_secure_chat/main.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_usb] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=ST7789LCDDisplay -; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 -; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_ble] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=ST7789LCDDisplay - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 ; dynamic, random PIN - -D AUTO_SHUTDOWN_MILLIVOLTS=3400 - -D BLE_DEBUG_LOGGING=1 - -D OFFLINE_QUEUE_SIZE=256 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_wifi] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=ST7789LCDDisplay - -D WIFI_DEBUG_LOGGING=1 - -D WIFI_SSID='"myssid"' - -D WIFI_PWD='"mypwd"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_sensor] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D ADVERT_NAME='"Heltec v4 Sensor"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=3 - -D ENV_PIN_SCL=4 - -D DISPLAY_CLASS=ST7789LCDDisplay -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - +<../examples/simple_sensor> -lib_deps = - ${heltec_v4_tft.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 0d2bd497..015c3a8e 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display(&(board.periph_power)); + DISPLAY_CLASS display; MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index 00d2adab..a153b2af 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -9,11 +9,7 @@ #include #include #ifdef DISPLAY_CLASS -#ifdef HELTEC_LORA_V4_OLED - #include -#elif defined(HELTEC_LORA_V4_TFT) - #include -#endif + #include #include #endif From d0f6def4f9b4d03ea226a9b2246aa594081a5049 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 27 Nov 2025 21:49:04 +0100 Subject: [PATCH 48/53] thinknode_m5: initial port --- src/helpers/ui/GxEPDDisplay.cpp | 8 + variants/thinknode_m5/ThinknodeM5Board.cpp | 40 ++++ variants/thinknode_m5/ThinknodeM5Board.h | 18 ++ variants/thinknode_m5/pins_arduino.h | 28 +++ variants/thinknode_m5/platformio.ini | 217 +++++++++++++++++++++ variants/thinknode_m5/target.cpp | 57 ++++++ variants/thinknode_m5/target.h | 32 +++ variants/thinknode_m5/variant.h | 21 ++ 8 files changed, 421 insertions(+) create mode 100644 variants/thinknode_m5/ThinknodeM5Board.cpp create mode 100644 variants/thinknode_m5/ThinknodeM5Board.h create mode 100644 variants/thinknode_m5/pins_arduino.h create mode 100644 variants/thinknode_m5/platformio.ini create mode 100644 variants/thinknode_m5/target.cpp create mode 100644 variants/thinknode_m5/target.h create mode 100644 variants/thinknode_m5/variant.h diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 34e31e30..a8a9b209 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -5,9 +5,17 @@ #define DISPLAY_ROTATION 3 #endif +#ifdef ESP32 + SPIClass SPI1 = SPIClass(FSPI); +#endif + bool GxEPDDisplay::begin() { display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); +#ifdef ESP32 + SPI1.begin(PIN_DISPLAY_SCLK, PIN_DISPLAY_MISO, PIN_DISPLAY_MOSI, PIN_DISPLAY_CS); +#else SPI1.begin(); +#endif display.init(115200, true, 2, false); display.setRotation(DISPLAY_ROTATION); setTextSize(1); // Default to size 1 diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp new file mode 100644 index 00000000..64744019 --- /dev/null +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -0,0 +1,40 @@ +#include "ThinknodeM5Board.h" + + + +void ThinknodeM5Board::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 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); + + 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* ThinknodeM5Board::getManufacturerName() const { + return "Elecrow ThinkNode M2"; + } diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h new file mode 100644 index 00000000..58a3ae30 --- /dev/null +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include + +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 ; + +}; \ No newline at end of file diff --git a/variants/thinknode_m5/pins_arduino.h b/variants/thinknode_m5/pins_arduino.h new file mode 100644 index 00000000..a5dee363 --- /dev/null +++ b/variants/thinknode_m5/pins_arduino.h @@ -0,0 +1,28 @@ +// 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 = GPS_TX; +static const uint8_t RX = GPS_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = P_LORA_NSS; +static const uint8_t SCK = P_LORA_SCLK; +static const uint8_t MOSI = P_LORA_MISO; +static const uint8_t MISO = P_LORA_MOSI; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = PIN_BOARD_SCL; +static const uint8_t SDA = PIN_BOARD_SDA; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini new file mode 100644 index 00000000..54ad8bd5 --- /dev/null +++ b/variants/thinknode_m5/platformio.ini @@ -0,0 +1,217 @@ +[ThinkNode_M5] +extends = esp32_base +board = ESP32-S3-WROOM-1-N4 +build_flags = ${esp32_base.build_flags} + -I variants/thinknode_m5 + -D THINKNODE_M5 + -D GPS_RX=19 + -D GPS_TX=20 + -D PIN_VEXT_EN=46 + -D PIN_BUZZER=9 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_BOARD_SCL=1 + -D PIN_BOARD_SDA=2 + -D P_LORA_DIO_1=4 + -D P_LORA_NSS=17 + -D P_LORA_RESET=6 ; RADIOLIB_NC + -D P_LORA_BUSY=5 ; DIO2 = 38 + -D P_LORA_SCLK=16 + -D P_LORA_MISO=7 + -D P_LORA_MOSI=15 + -D PIN_USER_BTN=21 + -D PIN_STATUS_LED=1 + -D LED_STATE_ON=HIGH + -D PIN_LED=3 + -D DISPLAY_ROTATION=4 + -D DISPLAY_CLASS=GxEPDDisplay + -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 + -D EINK_SCALE_X=1.5625f + -D EINK_SCALE_Y=1.5625f + -D EINK_X_OFFSET=0 + -D EINK_Y_OFFSET=10 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 + -D MESH_DEBUG=1 +build_src_filter = ${esp32_base.build_src_filter} + + + + + + + +<../variants/thinknode_m5> +lib_deps = ${esp32_base.lib_deps} + zinggjm/GxEPD2 @ 1.6.2 + bakercp/CRC32 @ ^2.0.0 + +[env:ThinkNode_M5_Repeater] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"Thinknode M2 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_M5.lib_deps} + ${esp32_ota.lib_deps} + +; [env:ThinkNode_M5_Repeater_bridge_rs232] +; extends = ThinkNode_M5 +; build_src_filter = ${ThinkNode_M5.build_src_filter} +; + +; +<../examples/simple_repeater/*.cpp> +; build_flags = +; ${ThinkNode_M5.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${ThinkNode_M5.lib_deps} +; ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_Repeater_bridge_espnow] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_room_server] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"Thinknode M2 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_M5.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_terminal_chat] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M5_companion_radio_ble] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.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 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_usb] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.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_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_wifi] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"Livebox-633C"' + -D WIFI_PWD='"vvQUHGSxsWd7fKMYSr"' +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_serial] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D SERIAL_TX=D6 + -D SERIAL_RX=D7 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp new file mode 100644 index 00000000..c65696c2 --- /dev/null +++ b/variants/thinknode_m5/target.cpp @@ -0,0 +1,57 @@ +#include +#include "target.h" + +ThinknodeM5Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +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); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); +// pinMode(21, INPUT); +// pinMode(48, OUTPUT); + #if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +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); // create new random identity +} + diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h new file mode 100644 index 00000000..75b68ae4 --- /dev/null +++ b/variants/thinknode_m5/target.h @@ -0,0 +1,32 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +//#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ThinknodeM5Board 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(); + + \ No newline at end of file diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h new file mode 100644 index 00000000..6118bd75 --- /dev/null +++ b/variants/thinknode_m5/variant.h @@ -0,0 +1,21 @@ +#define I2C_SCL 1 +#define I2C_SDA 2 +#define PIN_VBAT_READ 8 +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (2.11F) +#define PIN_BUZZER 9 +#define PIN_VEXT_EN_ACTIVE HIGH +#define PIN_VEXT_EN 46 +#define PIN_USER_BTN 21 +#define PIN_LED 3 +#define PIN_STATUS_LED 1 +#define PIN_PWRBTN 14 + +#define PIN_DISPLAY_MISO (-1) +#define PIN_DISPLAY_MOSI (45) +#define PIN_DISPLAY_SCLK (38) +#define PIN_DISPLAY_CS (39) +#define PIN_DISPLAY_DC (40) +#define PIN_DISPLAY_RST (41) +#define PIN_DISPLAY_BUSY (42) +#define DISP_BACKLIGHT (5) \ No newline at end of file From 24edd3cf209b9a8362d103b535d2f18c7958ae56 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 27 Nov 2025 22:55:21 +0100 Subject: [PATCH 49/53] thinknode_m5: add pca9557 expander --- variants/thinknode_m5/ThinknodeM5Board.cpp | 3 +-- variants/thinknode_m5/platformio.ini | 7 ++++--- variants/thinknode_m5/target.cpp | 6 ++++++ variants/thinknode_m5/target.h | 3 +++ variants/thinknode_m5/variant.h | 6 +++--- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 64744019..2aabb0e4 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -1,7 +1,6 @@ #include "ThinknodeM5Board.h" - void ThinknodeM5Board::begin() { pinMode(PIN_VEXT_EN, OUTPUT); digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle @@ -9,7 +8,7 @@ void ThinknodeM5Board::begin() { 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 + // pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 54ad8bd5..a78b1ba2 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -19,9 +19,9 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 - -D PIN_STATUS_LED=1 - -D LED_STATE_ON=HIGH - -D PIN_LED=3 +# -D PIN_STATUS_LED=1 ; leds are on PCA !!! +# -D LED_STATE_ON=HIGH +# -D PIN_LED=3 -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 @@ -45,6 +45,7 @@ build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 + maxpromer/PCA9557-arduino [env:ThinkNode_M5_Repeater] extends = ThinkNode_M5 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index c65696c2..2c388b58 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -15,6 +15,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +PCA9557 expander (0x18, &Wire1); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -26,6 +27,11 @@ bool radio_init() { rtc_clock.begin(Wire); // pinMode(21, INPUT); // pinMode(48, OUTPUT); + Wire1.begin(48, 47); + expander.pinMode(4, OUTPUT); // eink + expander.pinMode(5, OUTPUT); // peripherals + expander.digitalWrite(4, HIGH); + expander.digitalWrite(5, HIGH); #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); return radio.std_init(&spi); diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 75b68ae4..7fa749f8 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -12,11 +12,14 @@ #include #include #endif +#include +#include extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +extern PCA9557 expander; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 6118bd75..d312fcbf 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -7,8 +7,8 @@ #define PIN_VEXT_EN_ACTIVE HIGH #define PIN_VEXT_EN 46 #define PIN_USER_BTN 21 -#define PIN_LED 3 -#define PIN_STATUS_LED 1 +//#define PIN_LED 3 +//#define PIN_STATUS_LED 1 #define PIN_PWRBTN 14 #define PIN_DISPLAY_MISO (-1) @@ -18,4 +18,4 @@ #define PIN_DISPLAY_DC (40) #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) -#define DISP_BACKLIGHT (5) \ No newline at end of file +//#define DISP_BACKLIGHT (5) \ No newline at end of file From dfec6d3483432159c34d734ad1d31b60f3e6b28b Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 09:57:58 +0100 Subject: [PATCH 50/53] thinknode_m5: tx_led --- variants/thinknode_m5/ThinknodeM5Board.cpp | 16 ++++++++++------ variants/thinknode_m5/ThinknodeM5Board.h | 9 +++++++++ variants/thinknode_m5/platformio.ini | 4 ++-- variants/thinknode_m5/target.cpp | 8 ++------ variants/thinknode_m5/target.h | 2 -- variants/thinknode_m5/variant.h | 3 ++- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 2aabb0e4..f178caad 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -1,14 +1,18 @@ #include "ThinknodeM5Board.h" +PCA9557 expander (0x18, &Wire1); void ThinknodeM5Board::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 + // Start expander + Wire1.begin(48, 47); + expander.pinMode(EXP_PIN_POWER, OUTPUT); // eink + expander.pinMode(EXP_PIN_BACKLIGHT, OUTPUT); // peripherals + expander.pinMode(EXP_PIN_LED, OUTPUT); // peripherals + expander.digitalWrite(EXP_PIN_POWER, HIGH); + expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); + expander.digitalWrite(EXP_PIN_LED, LOW); + ESP32Board::begin(); - // pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h index 58a3ae30..3c120027 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.h +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -4,6 +4,9 @@ #include #include #include +#include + +extern PCA9557 expander; class ThinknodeM5Board : public ESP32Board { @@ -15,4 +18,10 @@ public: uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; + void onBeforeTransmit() override { + expander.digitalWrite(EXP_PIN_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + expander.digitalWrite(EXP_PIN_LED, LOW); // turn TX LED off + } }; \ No newline at end of file diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index a78b1ba2..11f56377 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -6,11 +6,10 @@ build_flags = ${esp32_base.build_flags} -D THINKNODE_M5 -D GPS_RX=19 -D GPS_TX=20 - -D PIN_VEXT_EN=46 -D PIN_BUZZER=9 - -D PIN_VEXT_EN_ACTIVE=HIGH -D PIN_BOARD_SCL=1 -D PIN_BOARD_SDA=2 + -D P_LORA_EN=46 -D P_LORA_DIO_1=4 -D P_LORA_NSS=17 -D P_LORA_RESET=6 ; RADIOLIB_NC @@ -19,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 + -D EXP_PIN_LED=1 # -D PIN_STATUS_LED=1 ; leds are on PCA !!! # -D LED_STATE_ON=HIGH # -D PIN_LED=3 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index 2c388b58..fdc5ca7a 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -15,7 +15,6 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; -PCA9557 expander (0x18, &Wire1); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -27,11 +26,8 @@ bool radio_init() { rtc_clock.begin(Wire); // pinMode(21, INPUT); // pinMode(48, OUTPUT); - Wire1.begin(48, 47); - expander.pinMode(4, OUTPUT); // eink - expander.pinMode(5, OUTPUT); // peripherals - expander.digitalWrite(4, HIGH); - expander.digitalWrite(5, HIGH); + pinMode(P_LORA_EN, OUTPUT); + digitalWrite(P_LORA_EN, HIGH); #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); return radio.std_init(&spi); diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 7fa749f8..c3584a70 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -12,8 +12,6 @@ #include #include #endif -#include -#include extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index d312fcbf..7ee5f5cc 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -18,4 +18,5 @@ #define PIN_DISPLAY_DC (40) #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) -//#define DISP_BACKLIGHT (5) \ No newline at end of file +#define EXP_PIN_BACKLIGHT (5) +#define EXP_PIN_POWER (4) \ No newline at end of file From ee4e87c3ee545821a9b017e73525a3f943d9f2ba Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 10:33:19 +0100 Subject: [PATCH 51/53] thinknode_m5: manage baclight --- examples/companion_radio/ui-new/UITask.cpp | 6 +++++- src/helpers/ui/GxEPDDisplay.cpp | 9 +++++++++ variants/thinknode_m5/ThinknodeM5Board.cpp | 3 +-- variants/thinknode_m5/platformio.ini | 3 +++ variants/thinknode_m5/target.cpp | 2 -- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 16751d20..fe26277f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -716,10 +716,14 @@ void UITask::loop() { _analogue_pin_read_millis = millis(); } #endif -#if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN) +#if defined(BACKLIGHT_BTN) if (millis() > next_backlight_btn_check) { bool touch_state = digitalRead(PIN_BUTTON2); +#if defined(DISP_BACKLIGHT) digitalWrite(DISP_BACKLIGHT, !touch_state); +#elif defined(EXP_PIN_BACKLIGHT) + expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state); +#endif next_backlight_btn_check = millis() + 300; } #endif diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index a8a9b209..ad47754b 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -1,6 +1,11 @@ #include "GxEPDDisplay.h" +#ifdef EXP_PIN_BACKLIGHT + #include + extern PCA9557 expander; +#endif + #ifndef DISPLAY_ROTATION #define DISPLAY_ROTATION 3 #endif @@ -35,6 +40,8 @@ void GxEPDDisplay::turnOn() { if (!_init) begin(); #if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN) digitalWrite(DISP_BACKLIGHT, HIGH); +#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN) + expander.digitalWrite(EXP_PIN_BACKLIGHT, HIGH); #endif _isOn = true; } @@ -42,6 +49,8 @@ void GxEPDDisplay::turnOn() { void GxEPDDisplay::turnOff() { #if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN) digitalWrite(DISP_BACKLIGHT, LOW); +#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN) + expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); #endif _isOn = false; } diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index f178caad..916f4483 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -3,7 +3,7 @@ PCA9557 expander (0x18, &Wire1); void ThinknodeM5Board::begin() { - // Start expander + // Start expander and configure pins Wire1.begin(48, 47); expander.pinMode(EXP_PIN_POWER, OUTPUT); // eink expander.pinMode(EXP_PIN_BACKLIGHT, OUTPUT); // peripherals @@ -11,7 +11,6 @@ void ThinknodeM5Board::begin() { expander.digitalWrite(EXP_PIN_POWER, HIGH); expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); expander.digitalWrite(EXP_PIN_LED, LOW); - ESP32Board::begin(); } diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 11f56377..353a9c52 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -18,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 + -D PIN_BUTTON2=14 -D EXP_PIN_LED=1 # -D PIN_STATUS_LED=1 ; leds are on PCA !!! # -D LED_STATE_ON=HIGH @@ -29,6 +30,8 @@ build_flags = ${esp32_base.build_flags} -D EINK_SCALE_Y=1.5625f -D EINK_X_OFFSET=0 -D EINK_Y_OFFSET=10 + -D BACKLIGHT_BTN=PIN_BUTTON2 + -D AUTO_OFF_MILLIS=0 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=3.3 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index fdc5ca7a..fa559610 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -24,8 +24,6 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); -// pinMode(21, INPUT); -// pinMode(48, OUTPUT); pinMode(P_LORA_EN, OUTPUT); digitalWrite(P_LORA_EN, HIGH); #if defined(P_LORA_SCLK) From 1c0017b634a68a12e232c46b2c7b4d4d47759858 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 11:11:13 +0100 Subject: [PATCH 52/53] thinknode_m5: gps support --- examples/companion_radio/ui-new/UITask.cpp | 15 +++++++++++++-- .../sensors/EnvironmentSensorManager.cpp | 4 ++++ variants/thinknode_m5/ThinknodeM5Board.cpp | 5 +++++ variants/thinknode_m5/pins_arduino.h | 4 ++-- variants/thinknode_m5/platformio.ini | 19 +++++++++++-------- variants/thinknode_m5/target.cpp | 9 ++++++++- variants/thinknode_m5/target.h | 4 +++- variants/thinknode_m5/variant.h | 8 +++++++- 8 files changed, 53 insertions(+), 15 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index fe26277f..59a1b2de 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -260,13 +260,24 @@ public: #if ENV_INCLUDE_GPS == 1 } else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); + char buf[50]; int y = 18; - display.drawTextLeftAlign(0, y, _task->getGPSState() ? "gps on" : "gps off"); + bool gps_state = _task->getGPSState(); +#ifdef PIN_GPS_SWITCH + bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); + if (gps_state != hw_gps_state) { + strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); + } else { + strcpy(buf, gps_state ? "gps on" : "gps off"); + } +#else + strcpy(buf, gps_state ? "gps on" : "gps off"); +#endif + display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { y = y + 12; display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { - char buf[50]; strcpy(buf, nmea->isValid()?"fix":"no fix"); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 79dc87e5..bb675c27 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -548,7 +548,11 @@ void EnvironmentSensorManager::initBasicGPS() { delay(1000); // We'll consider GPS detected if we see any data on Serial1 +#ifdef ENV_SKIP_GPS_DETECT + gps_detected = true; +#else gps_detected = (Serial1.available() > 0); +#endif if (gps_detected) { MESH_DEBUG_PRINTLN("GPS detected"); diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 916f4483..5adc8c00 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -11,6 +11,11 @@ void ThinknodeM5Board::begin() { expander.digitalWrite(EXP_PIN_POWER, HIGH); expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); expander.digitalWrite(EXP_PIN_LED, LOW); + +#ifdef PIN_GPS_SWITCH + pinMode(PIN_GPS_SWITCH, INPUT); +#endif + ESP32Board::begin(); } diff --git a/variants/thinknode_m5/pins_arduino.h b/variants/thinknode_m5/pins_arduino.h index a5dee363..408ed236 100644 --- a/variants/thinknode_m5/pins_arduino.h +++ b/variants/thinknode_m5/pins_arduino.h @@ -12,8 +12,8 @@ #define USB_PID 0x1001 // Serial -static const uint8_t TX = GPS_TX; -static const uint8_t RX = GPS_RX; +static const uint8_t TX = PIN_GPS_TX; +static const uint8_t RX = PIN_GPS_RX; // Default SPI will be mapped to Radio static const uint8_t SS = P_LORA_NSS; diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 353a9c52..23db506a 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,9 +3,8 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 + -I src/helpres/sensors -D THINKNODE_M5 - -D GPS_RX=19 - -D GPS_TX=20 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 -D PIN_BOARD_SDA=2 @@ -19,10 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 -D PIN_BUTTON2=14 - -D EXP_PIN_LED=1 -# -D PIN_STATUS_LED=1 ; leds are on PCA !!! -# -D LED_STATE_ON=HIGH -# -D PIN_LED=3 + -D EXP_PIN_LED=1 ; led is on bus expander -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 @@ -32,6 +28,7 @@ build_flags = ${esp32_base.build_flags} -D EINK_Y_OFFSET=10 -D BACKLIGHT_BTN=PIN_BUTTON2 -D AUTO_OFF_MILLIS=0 + -D DISABLE_DIAGNOSTIC_OUTPUT -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=3.3 -D SX126X_CURRENT_LIMIT=140 @@ -40,7 +37,11 @@ build_flags = ${esp32_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_RX_BOOSTED_GAIN=1 -D MESH_DEBUG=1 + -D ENV_INCLUDE_GPS=1 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 build_src_filter = ${esp32_base.build_src_filter} + + + + + @@ -49,6 +50,7 @@ lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 maxpromer/PCA9557-arduino + stevemarple/MicroNMEA @ ^2.0.6 [env:ThinkNode_M5_Repeater] extends = ThinkNode_M5 @@ -109,7 +111,7 @@ lib_deps = ${esp32_ota.lib_deps} [env:ThinkNode_M5_room_server] -extends = ThinkNode_M5 +extends = ThinkNonde_M5 build_src_filter = ${ThinkNode_M5.build_src_filter} +<../examples/simple_room_server> build_flags = @@ -148,9 +150,10 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 +; -D GPS_NMEA_DEBUG build_src_filter = ${ThinkNode_M5.build_src_filter} + + diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index fa559610..8208d2c4 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -1,5 +1,6 @@ #include #include "target.h" +#include ThinknodeM5Board board; @@ -14,7 +15,13 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +#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; diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index c3584a70..2af42095 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #ifdef DISPLAY_CLASS #include #include @@ -16,7 +18,7 @@ extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; extern PCA9557 expander; #ifdef DISPLAY_CLASS diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 7ee5f5cc..9b82416b 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -19,4 +19,10 @@ #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) #define EXP_PIN_BACKLIGHT (5) -#define EXP_PIN_POWER (4) \ No newline at end of file +#define EXP_PIN_POWER (4) + +#define PIN_GPS_EN (11) +#define PIN_GPS_RESET (13) +#define PIN_GPS_RX (20) +#define PIN_GPS_TX (19) +#define PIN_GPS_SWITCH (10) \ No newline at end of file From e054597a189cd7749c5327351d27e9dc92d0d125 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 30 Nov 2025 18:32:10 +1100 Subject: [PATCH 53/53] * ver 1.11.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 9c22532d..1fcc5697 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "13 Nov 2025" +#define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.10.0" +#define FIRMWARE_VERSION "v1.11.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 98bce787..ed9f0c5f 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -68,11 +68,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 8641caaf..e7f1fee8 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 9259ad9c..c320eb44 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #define FIRMWARE_ROLE "sensor"