From 00e0635ab5400c9fc78bf05c729ddb1f234f3a9a Mon Sep 17 00:00:00 2001 From: brad Date: Sat, 8 Nov 2025 20:05:46 -0600 Subject: [PATCH 01/20] 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 850d57a8f243dce0193008ee46b2f276d4fa5919 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 14 Nov 2025 21:51:28 -0700 Subject: [PATCH 02/20] 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 310618e6899337b37fb3a9268f8e6571bf1f760c Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 19 Nov 2025 11:43:52 +0800 Subject: [PATCH 03/20] 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 2bd47de3b9824d99450baa4d1d90c9729c13032f Mon Sep 17 00:00:00 2001 From: zaquaz Date: Thu, 20 Nov 2025 18:55:39 -0800 Subject: [PATCH 04/20] 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 031fa1e704da52c8d31dba1799fbf19b65ed14ba Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Thu, 20 Nov 2025 21:58:42 -0800 Subject: [PATCH 05/20] 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 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 06/20] 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 07/20] 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 08/20] 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 09/20] 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 10/20] 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 11/20] 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 12/20] 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 13/20] 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 14/20] 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 15/20] 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 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 16/20] 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 17/20] 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 18/20] 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 19/20] 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 20/20] 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