From b9bdbe4de776998ae17ef880f1e8be1dfb0fea61 Mon Sep 17 00:00:00 2001 From: Normunds Gavars Date: Fri, 12 Dec 2025 18:13:47 +0200 Subject: [PATCH 01/44] use EnvironmentSensorManager in t114 variant --- variants/heltec_t114/platformio.ini | 8 ++- variants/heltec_t114/target.cpp | 101 +++------------------------- variants/heltec_t114/target.h | 23 +------ variants/heltec_t114/variant.h | 6 ++ 4 files changed, 25 insertions(+), 113 deletions(-) diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 7b6f5ceef..377fbae29 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -6,6 +6,7 @@ extends = nrf52_base board = heltec_t114 board_build.ldscript = boards/nrf52840_s140_v6.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_6.1.1_API/include -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 -I variants/heltec_t114 @@ -36,10 +37,11 @@ build_flags = ${nrf52_base.build_flags} -D PIN_GPS_RESET_ACTIVE=LOW build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/heltec_t114> lib_deps = ${nrf52_base.lib_deps} - stevemarple/MicroNMEA @ ^2.0.6 + ${sensor_base.lib_deps} adafruit/Adafruit GFX Library @ ^1.12.1 debug_tool = jlink upload_protocol = nrfutil @@ -56,6 +58,8 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -167,6 +171,8 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index c3341103a..6202b4599 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -1,7 +1,6 @@ #include #include "target.h" #include -#include T114Board board; @@ -11,8 +10,15 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); -T114SensorManager sensors = T114SensorManager(nmea); + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -43,91 +49,4 @@ void radio_set_tx_power(uint8_t dbm) { mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity -} - -void T114SensorManager::start_gps() { - if (!gps_active) { - gps_active = true; - _location->begin(); - } -} - -void T114SensorManager::stop_gps() { - if (gps_active) { - gps_active = false; - _location->stop(); - } -} - -bool T114SensorManager::begin() { - Serial1.begin(9600); - - // Try to detect if GPS is physically connected to determine if we should expose the setting - pinMode(GPS_EN, OUTPUT); - digitalWrite(GPS_EN, HIGH); // Power on GPS - - // Give GPS a moment to power up and send data - delay(1500); - - // We'll consider GPS detected if we see any data on Serial1 - gps_detected = (Serial1.available() > 0); - - if (gps_detected) { - MESH_DEBUG_PRINTLN("GPS detected"); - } else { - MESH_DEBUG_PRINTLN("No GPS detected"); - } - digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed - - return true; -} - -bool T114SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { - if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); - } - return true; -} - -void T114SensorManager::loop() { - static long next_gps_update = 0; - - _location->loop(); - - if (millis() > next_gps_update) { - if (_location->isValid()) { - node_lat = ((double)_location->getLatitude())/1000000.; - node_lon = ((double)_location->getLongitude())/1000000.; - node_altitude = ((double)_location->getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); - } - next_gps_update = millis() + 1000; - } -} - -int T114SensorManager::getNumSettings() const { - return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected -} - -const char* T114SensorManager::getSettingName(int i) const { - return (gps_detected && i == 0) ? "gps" : NULL; -} - -const char* T114SensorManager::getSettingValue(int i) const { - if (gps_detected && i == 0) { - return gps_active ? "1" : "0"; - } - return NULL; -} - -bool T114SensorManager::setSettingValue(const char* name, const char* value) { - if (gps_detected && strcmp(name, "gps") == 0) { - if (strcmp(value, "0") == 0) { - stop_gps(); - } else { - start_gps(); - } - return true; - } - return false; // not supported -} +} \ No newline at end of file diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 6306cd699..d2cd76669 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -6,8 +6,6 @@ #include #include #include -#include -#include #ifdef DISPLAY_CLASS #include @@ -18,29 +16,12 @@ #endif #endif -class T114SensorManager : public SensorManager { - bool gps_active = false; - bool gps_detected = false; - LocationProvider* _location; - - void start_gps(); - void stop_gps(); -public: - T114SensorManager(LocationProvider &location): _location(&location) { } - bool begin() override; - bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; - void loop() override; - LocationProvider* getLocationProvider() override { return gps_detected ? _location : NULL; } - int getNumSettings() const override; - const char* getSettingName(int i) const override; - const char* getSettingValue(int i) const override; - bool setSettingValue(const char* name, const char* value) override; -}; +#include extern T114Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern T114SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index b3f760bbb..d635b2e1b 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -50,9 +50,15 @@ //////////////////////////////////////////////////////////////////////////////// // I2C pin definition +#define WIRE_INTERFACES_COUNT 2 + #define PIN_WIRE_SDA (26) // P0.26 #define PIN_WIRE_SCL (27) // P0.27 +#define PIN_WIRE1_SDA (16) // P0.16 +#define PIN_WIRE1_SCL (13) // P0.13 + + //////////////////////////////////////////////////////////////////////////////// // SPI pin definition From 906f866f6e1246410cdf1257adc6da4210f2e943 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 22:04:24 +0100 Subject: [PATCH 02/44] thinknode_m3: initial commit --- boards/thinknode_m3.json | 72 ++++++++++++ variants/thinknode_m3/ThinknodeM3Board.cpp | 80 ++++++++++++++ variants/thinknode_m3/ThinknodeM3Board.h | 68 ++++++++++++ variants/thinknode_m3/platformio.ini | 122 +++++++++++++++++++++ variants/thinknode_m3/target.cpp | 99 +++++++++++++++++ variants/thinknode_m3/target.h | 29 +++++ variants/thinknode_m3/variant.cpp | 95 ++++++++++++++++ variants/thinknode_m3/variant.h | 109 ++++++++++++++++++ 8 files changed, 674 insertions(+) create mode 100644 boards/thinknode_m3.json create mode 100644 variants/thinknode_m3/ThinknodeM3Board.cpp create mode 100644 variants/thinknode_m3/ThinknodeM3Board.h create mode 100644 variants/thinknode_m3/platformio.ini create mode 100644 variants/thinknode_m3/target.cpp create mode 100644 variants/thinknode_m3/target.h create mode 100644 variants/thinknode_m3/variant.cpp create mode 100644 variants/thinknode_m3/variant.h diff --git a/boards/thinknode_m3.json b/boards/thinknode_m3.json new file mode 100644 index 000000000..617740b61 --- /dev/null +++ b/boards/thinknode_m3.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ] + ], + "usb_product": "elecrow_eink", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M3", + "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", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "elecrow nrf", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ] + }, + "url": "https://github.com/Elecrow-RD", + "vendor": "ELECROW" +} \ No newline at end of file diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinknodeM3Board.cpp new file mode 100644 index 000000000..74019fcbb --- /dev/null +++ b/variants/thinknode_m3/ThinknodeM3Board.cpp @@ -0,0 +1,80 @@ +#include +#include "ThinknodeM3Board.h" +#include + +#include + +void ThinknodeM3Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + btn_prev_state = HIGH; + + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + + // Enable DC/DC converter for improved power efficiency + NRF_POWER->DCDCEN = 1; + + Wire.begin(); + + delay(10); // give sx1262 some time to power up +} + +#if 0 +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"); +} + + +bool TrackerThinknodeM3Board::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("T1000E_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 \ No newline at end of file diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinknodeM3Board.h new file mode 100644 index 000000000..c9b962737 --- /dev/null +++ b/variants/thinknode_m3/ThinknodeM3Board.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +#define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX) + +class ThinknodeM3Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + uint8_t btn_prev_state; + +public: + void begin(); + + uint16_t getBattMilliVolts() override { + int adcvalue = 0; + + analogReference(AR_INTERNAL_2_4); + analogReadResolution(ADC_RESOLUTION); + delay(10); + + // ADC range is 0..2400mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * ADC_FACTOR); + } + + uint8_t getStartupReason() const override { return startup_reason; } + + #if defined(P_LORA_TX_LED) + #if !defined(P_LORA_TX_LED_ON) + #define P_LORA_TX_LED_ON HIGH + #endif + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, P_LORA_TX_LED_ON); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, !P_LORA_TX_LED_ON); // turn TX LED off + } + #endif + + const char* getManufacturerName() const override { + return "Elecrow ThinkNode M3"; + } + + int buttonStateChanged() { + #ifdef BUTTON_PIN + uint8_t v = digitalRead(BUTTON_PIN); + if (v != btn_prev_state) { + btn_prev_state = v; + return (v == LOW) ? 1 : -1; + } + #endif + return 0; + } + + void powerOff() override { + sd_power_system_off(); + } + + void reboot() override { + NVIC_SystemReset(); + } + +// bool startOTAUpdate(const char* id, char reply[]) override; +}; \ No newline at end of file diff --git a/variants/thinknode_m3/platformio.ini b/variants/thinknode_m3/platformio.ini new file mode 100644 index 000000000..8ef2ba54a --- /dev/null +++ b/variants/thinknode_m3/platformio.ini @@ -0,0 +1,122 @@ +[ThinkNode_M3] +extends = nrf52_base +board = thinknode_m3 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/thinknode_m3 + -I src/helpers/ui + -D THINKNODE_M3 + -D PIN_USER_BTN=12 + -D USER_BTN_PRESSED=LOW + -D PIN_STATUS_LED=35 + -D RADIO_CLASS=CustomLR1110 + -D WRAPPER_CLASS=CustomLR1110Wrapper + -D LORA_TX_POWER=22 + -D RF_SWITCH_TABLE + -D RX_BOOSTED_GAIN=true + -D P_LORA_BUSY=43 + -D P_LORA_SCLK=45 + -D P_LORA_NSS=44 + -D P_LORA_DIO_1=40 + -D P_LORA_MISO=47 + -D P_LORA_MOSI=46 + -D P_LORA_RESET=42 + -D P_LORA_TX_LED=PIN_LED_BLUE + -D P_LORA_TX_LED_ON=LOW + -D LR11X0_DIO_AS_RF_SWITCH=true + -D LR11X0_DIO3_TCXO_VOLTAGE=3.3 + -D MESH_DEBUG=1 + -D ENV_INCLUDE_GPS=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/thinknode_m3> + + +debug_tool = stlink +upload_protocol = nrfutil +lib_deps= ${nrf52_base.lib_deps} + +[env:ThinkNode_M3_repeater] +extends = ThinkNode_M3 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D ADVERT_NAME='"ThinkNode_M3 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<../examples/simple_repeater> +lib_deps = ${ThinkNode_M3.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:ThinkNode_M3_room_server] +extends = ThinkNode_M3 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D ADVERT_NAME='"ThinkNode_M3 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 + -D RF_SWITCH_TABLE +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<../examples/simple_room_server> +lib_deps = ${ThinkNode_M3.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:ThinkNode_M3_companion_radio_usb] +extends = ThinkNode_M3 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_BUZZER=23 + -D PIN_BUZZER_EN=36 +build_src_filter = ${ThinkNode_M3.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${ThinkNode_M3.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M3_companion_radio_ble] +extends = ThinkNode_M3 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_TX_POWER=0 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 + -D GPS_NMEA_DEBUG + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_BUZZER=23 + -D PIN_BUZZER_EN=36 +build_src_filter = ${ThinkNode_M3.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${ThinkNode_M3.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp new file mode 100644 index 000000000..c6708e4d1 --- /dev/null +++ b/variants/thinknode_m3/target.cpp @@ -0,0 +1,99 @@ +#include +#include "target.h" +#include + +ThinknodeM3Board 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); +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + NullDisplayDriver display; +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +#ifdef RF_SWITCH_TABLE +static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC, + RADIOLIB_NC, + RADIOLIB_NC +}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + { LR11x0::MODE_STBY, {LOW , LOW }}, + { LR11x0::MODE_RX, {HIGH, LOW }}, + { LR11x0::MODE_TX, {HIGH, HIGH }}, + { LR11x0::MODE_TX_HP, {LOW , HIGH }}, + { LR11x0::MODE_TX_HF, {LOW , LOW }}, + { LR11x0::MODE_GNSS, {LOW , LOW }}, + { LR11x0::MODE_WIFI, {LOW , LOW }}, + END_OF_MODE_TABLE, +}; +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + +#ifdef LR11X0_DIO3_TCXO_VOLTAGE + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(2); + radio.explicitHeader(); + +#ifdef RF_SWITCH_TABLE + radio.setRfSwitchTable(rfswitch_dios, rfswitch_table); +#endif +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + + return true; // success +} + +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 +} \ No newline at end of file diff --git a/variants/thinknode_m3/target.h b/variants/thinknode_m3/target.h new file mode 100644 index 000000000..f60a85b03 --- /dev/null +++ b/variants/thinknode_m3/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include "ThinknodeM3Board.h" +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include "NullDisplayDriver.h" +#endif + +#ifdef DISPLAY_CLASS + extern NullDisplayDriver display; +#endif + +extern ThinknodeM3Board 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/thinknode_m3/variant.cpp b/variants/thinknode_m3/variant.cpp new file mode 100644 index 000000000..dad0f3f55 --- /dev/null +++ b/variants/thinknode_m3/variant.cpp @@ -0,0 +1,95 @@ +/* + * variant.cpp + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = +{ + 0, // P0.00 + 1, // P0.01 + 2, // P0.02 + 3, // P0.03 + 4, // P0.04 + 5, // P0.05 + 6, // P0.06 + 7, // P0.07 + 8, // P0.08 + 9, // P0.09 + 10, // P0.10 + 11, // P0.11 + 12, // P0.12 + 13, // P0.13 + 14, // P0.14 + 15, // P0.15 + 16, // P0.16 + 17, // P0.17 + 18, // P0.18 + 19, // P0.19 + 20, // P0.20 + 21, // P0.21 + 22, // P0.22 + 23, // P0.23 + 24, // P0.24 + 25, // P0.25 + 26, // P0.26 + 27, // P0.27 + 28, // P0.28 + 29, // P0.29 + 30, // P0.30 + 31, // P0.31 + 32, // P1.00 + 33, // P1.01 + 34, // P1.02 + 35, // P1.03 + 36, // P1.04 + 37, // P1.05 + 38, // P1.06 + 39, // P1.07 + 40, // P1.08 + 41, // P1.09 + 42, // P1.10 + 43, // P1.11 + 44, // P1.12 + 45, // P1.13 + 46, // P1.14 + 47, // P1.15 +}; + +void initVariant() +{ +/* TODO */ + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(BAT_POWER, OUTPUT); + digitalWrite(BAT_POWER, HIGH); + pinMode(EEPROM_POWER, OUTPUT); + digitalWrite(EEPROM_POWER, HIGH); + + pinMode(36, OUTPUT); + digitalWrite(36, HIGH); + pinMode(34, OUTPUT); + digitalWrite(34, HIGH); + + pinMode(LED_POWER, OUTPUT); + digitalWrite(LED_POWER, HIGH); + + pinMode(PIN_LED_BLUE, OUTPUT); + pinMode(PIN_LED_GREEN, OUTPUT); + pinMode(PIN_LED_RED, OUTPUT); + + pinMode(BUTTON_PIN, INPUT_PULLUP); + + pinMode(PIN_GPS_POWER, OUTPUT); + pinMode(PIN_GPS_EN, OUTPUT); + pinMode(PIN_GPS_RESET, OUTPUT); + + // Power on gps but in standby + digitalWrite(PIN_GPS_EN, LOW); + digitalWrite(PIN_GPS_POWER, HIGH); +} diff --git a/variants/thinknode_m3/variant.h b/variants/thinknode_m3/variant.h new file mode 100644 index 000000000..02ed78a84 --- /dev/null +++ b/variants/thinknode_m3/variant.h @@ -0,0 +1,109 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) +// #define USE_LFRC // 32.768 kHz RC oscillator + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define NRF_APM // detect usb power + + +#define EXT_CHRG_DETECT (32) // P1.3 +#define EXT_PWR_DETECT (31) // P0.5 + +#define PIN_VBAT_READ (5) +#define AREF_VOLTAGE (2.4f) +#define ADC_MULTIPLIER (2.0) //(1.75f) +// 2.0 gives more coherent value, 4.2V when charged, needs tweaking +#define ADC_RESOLUTION (12) +#define ADC_MAX (4096) + +#define EEPROM_POWER (7) +#define BAT_POWER (17) +#define PIN_PWR_EN (16) + + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define HAS_WIRE (1) +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (26) // P0.26 +#define PIN_WIRE_SCL (27) // P0.27 +#define I2C_NO_RESCAN + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (47) // P1.15 +#define PIN_SPI_MOSI (46) // P1.14 +#define PIN_SPI_SCK (45) // P1.13 +#define PIN_SPI_NSS (44) // P1.12 + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_POWER (29) +#define LED_BLUE (-1) // No blue led +#define PIN_LED_BLUE (37) +#define PIN_LED_GREEN (35) // P0.24 +#define PIN_LED_RED (33) +#define LED_PIN PIN_LED_GREEN +#define LED_BUILTIN PIN_LED_BLUE +#define LED_STATE_ON LOW + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (12) // P0.12 +#define BUTTON_PIN PIN_BUTTON1 + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define HAS_GPS 1 +#define PIN_GPS_RX (22) +#define PIN_GPS_TX (20) + +#define PIN_GPS_POWER (14) +#define PIN_GPS_EN (21) // STANDBY +#define PIN_GPS_RESET (25) // REINIT +#define GPS_RESET_ACTIVE LOW +#define GPS_EN_ACTIVE HIGH +#define GPS_BAUDRATE 9600 + +//////////////////////////////////////////////////////////////////////////////// +// Buzzer + +#define BUZZER_EN (37) // P1.5 +#define BUZZER_PIN (25) // P0.25 \ No newline at end of file From 450f59f6bb84f5223c1945ea086f6bb71fa23514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sun, 23 Nov 2025 14:25:38 +0000 Subject: [PATCH 03/44] Add devcontainer configuration for vscode --- .devcontainer/devcontainer.json | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..b734fe6b9 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,42 @@ +{ + "name": "MeshCore", + "image": "mcr.microsoft.com/devcontainers/python:3-bookworm", + "features": { + "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { + "packages": [ + "sudo" + ] + } + }, + "runArgs": [ + "--network=host", + "--privileged", + "--volume", + "/dev/bus/usb:/dev/bus/usb" + ], + "postCreateCommand": { + "platformio": "pipx install platformio" + }, + "customizations": { + "vscode": { + "settings": { + "platformio-ide.disablePIOHomeStartup": true, + "editor.formatOnSave": false, + "workbench.colorCustomizations": { + "titleBar.activeBackground": "#0d1a2b", + "titleBar.activeForeground": "#ffffff", + "titleBar.inactiveBackground": "#0d1a2b99", + "titleBar.inactiveForeground": "#ffffff99" + } + }, + "extensions": [ + "platformio.platformio-ide", + "github.vscode-github-actions", + "GitHub.vscode-pull-request-github" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] + } + } +} \ No newline at end of file From abc2aa0e9e03ac0aed5c7d3a9545f8f3ad2ff7d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Fri, 12 Dec 2025 17:24:28 +0000 Subject: [PATCH 04/44] Refactor devcontainer runArgs --- .devcontainer/devcontainer.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b734fe6b9..fcde50484 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,10 +9,12 @@ } }, "runArgs": [ - "--network=host", "--privileged", - "--volume", - "/dev/bus/usb:/dev/bus/usb" + // arch tty* is owned by uucp (986) + // debian tty* is owned by uucp (20) - no change needed + "--group-add=986", + "--network=host", + "--volume=/dev/bus/usb:/dev/bus/usb:ro" ], "postCreateCommand": { "platformio": "pipx install platformio" From 5b7b816fd5976bdda9dc79087001f69ad6711ac4 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 12 Dec 2025 19:01:15 +0700 Subject: [PATCH 05/44] Added default temperature from ESP32 MCU and NRF52 MCU Added NRF52Board.h and NRF52Board.cpp Modified NRF52 variants to extend from NRF52Board to share common feature --- examples/simple_repeater/MyMesh.cpp | 6 +++++ src/MeshCore.h | 2 ++ src/helpers/ESP32Board.h | 5 ++++ src/helpers/NRF52Board.cpp | 23 +++++++++++++++++++ src/helpers/NRF52Board.h | 12 ++++++++++ variants/heltec_mesh_solar/MeshSolarBoard.h | 3 ++- variants/heltec_t114/T114Board.h | 3 ++- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 3 ++- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 3 ++- variants/keepteen_lt1/KeepteenLT1Board.h | 3 ++- variants/lilygo_techo/TechoBoard.h | 3 ++- variants/lilygo_techo_lite/TechoBoard.h | 3 ++- variants/mesh_pocket/MeshPocket.h | 3 ++- .../MinewsemiME25LS01Board.h | 3 ++- variants/nano_g2_ultra/nano-g2.h | 3 ++- variants/promicro/PromicroBoard.h | 3 ++- variants/rak4631/RAK4631Board.h | 3 ++- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 3 ++- variants/sensecap_solar/SenseCapSolarBoard.h | 3 ++- variants/t1000-e/T1000eBoard.h | 3 ++- variants/thinknode_m1/ThinkNodeM1Board.h | 3 ++- variants/wio-tracker-l1/WioTrackerL1Board.h | 3 ++- variants/wio_wm1110/WioWM1110Board.h | 3 ++- variants/xiao_nrf52/XiaoNrf52Board.h | 3 ++- 24 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 src/helpers/NRF52Board.cpp create mode 100644 src/helpers/NRF52Board.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5fb1a729f..39ecd1055 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -173,6 +173,12 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + + float temperature = (float)board.getMCUTemperature(); + if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN + telemetry.addTemperature(TELEM_CHANNEL_SELF, (float)board.getMCUTemperature()); // Built-in MCU Temperature + } + // query other sensors -- target specific if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { perm_mask = 0x00; // just base telemetry allowed diff --git a/src/MeshCore.h b/src/MeshCore.h index 11a6a5b41..eb7940589 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -1,6 +1,7 @@ #pragma once #include +#include #define MAX_HASH_SIZE 8 #define PUB_KEY_SIZE 32 @@ -42,6 +43,7 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; + virtual float getMCUTemperature() { return NAN; } virtual bool setAdcMultiplier(float multiplier) { return false; }; virtual float getAdcMultiplier() const { return 0.0f; } virtual const char* getManufacturerName() const = 0; diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index e566f9293..64c92c43d 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -42,6 +42,11 @@ class ESP32Board : public mesh::MainBoard { #endif } + // Temperature from ESP32 MCU + float getMCUTemperature() override { + return temperatureRead(); + } + uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp new file mode 100644 index 000000000..ee50fe4c6 --- /dev/null +++ b/src/helpers/NRF52Board.cpp @@ -0,0 +1,23 @@ +#if defined(NRF52_PLATFORM) +#include "NRF52Board.h" + +// Temperature from NRF52 MCU +float NRF52Board::getMCUTemperature() { + NRF_TEMP->TASKS_START = 1; // Start temperature measurement + + long startTime = millis(); + while (NRF_TEMP->EVENTS_DATARDY == 0) { // Wait for completion. Should complete in 50us + if(millis() - startTime > 5) { // To wait 5ms just in case + NRF_TEMP->TASKS_STOP = 1; + return NAN; + } + } + + NRF_TEMP->EVENTS_DATARDY = 0; // Clear event flag + + int32_t temp = NRF_TEMP->TEMP; // In 0.25 *C units + NRF_TEMP->TASKS_STOP = 1; + + return temp * 0.25f; // Convert to *C +} +#endif diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h new file mode 100644 index 000000000..1a6f879f3 --- /dev/null +++ b/src/helpers/NRF52Board.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#if defined(NRF52_PLATFORM) + +class NRF52Board : public mesh::MainBoard { +public: + float getMCUTemperature() override; +}; +#endif \ No newline at end of file diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 3bec144f7..688a8e886 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef HELTEC_MESH_SOLAR #include "meshSolarApp.h" @@ -20,7 +21,7 @@ #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -class MeshSolarBoard : public mesh::MainBoard { +class MeshSolarBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 0f7fc47fa..bd9287e28 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -2,13 +2,14 @@ #include #include +#include // built-ins #define PIN_VBAT_READ 4 #define PIN_BAT_CTL 6 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class T114Board : public mesh::MainBoard { +class T114Board : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index 8484085b0..ac3069776 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -2,10 +2,11 @@ #include #include +#include #ifdef XIAO_NRF52 -class IkokaNanoNRFBoard : public mesh::MainBoard { +class IkokaNanoNRFBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 4a061d42f..8f817ff13 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -2,10 +2,11 @@ #include #include +#include #ifdef XIAO_NRF52 -class IkokaStickNRFBoard : public mesh::MainBoard { +class IkokaStickNRFBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index 9892638b2..e8c444ed5 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -2,8 +2,9 @@ #include #include +#include -class KeepteenLT1Board : public mesh::MainBoard { +class KeepteenLT1Board : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 080387976..91dfb5abb 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -2,6 +2,7 @@ #include #include +#include // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 @@ -12,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public mesh::MainBoard { +class TechoBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 4792153a9..79b5610c7 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -2,6 +2,7 @@ #include #include +#include // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 @@ -12,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public mesh::MainBoard { +class TechoBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 8f5b09c9f..876e3810a 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -2,13 +2,14 @@ #include #include +#include // built-ins #define PIN_VBAT_READ 29 #define PIN_BAT_CTL 34 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class HeltecMeshPocket : public mesh::MainBoard { +class HeltecMeshPocket : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 777606a6d..18aa9e8a4 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -2,6 +2,7 @@ #include #include +#include // LoRa and SPI pins @@ -20,7 +21,7 @@ #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class MinewsemiME25LS01Board : public mesh::MainBoard { +class MinewsemiME25LS01Board : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 5cedb0f99..7ed1c74b0 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -4,6 +4,7 @@ #include #include +#include // LoRa radio module pins #define P_LORA_DIO_1 (32 + 10) @@ -34,7 +35,7 @@ #define PIN_VBAT_READ (0 + 2) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class NanoG2Ultra : public mesh::MainBoard { +class NanoG2Ultra : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index dc20e5500..916afefd5 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -2,6 +2,7 @@ #include #include +#include #define P_LORA_NSS 13 //P1.13 45 #define P_LORA_DIO_1 11 //P0.10 10 @@ -19,7 +20,7 @@ #define PIN_VBAT_READ 17 #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class PromicroBoard : public mesh::MainBoard { +class PromicroBoard : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index 7f3a8fea9..2040bf41d 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -2,6 +2,7 @@ #include #include +#include // LoRa radio module pins for RAK4631 #define P_LORA_DIO_1 47 @@ -28,7 +29,7 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAK4631Board : public mesh::MainBoard { +class RAK4631Board : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index e5104a58d..fe554fe64 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -2,12 +2,13 @@ #include #include +#include // built-ins #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAKWismeshTagBoard : public mesh::MainBoard { +class RAKWismeshTagBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index b1e5f8f17..999c7783a 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -2,8 +2,9 @@ #include #include +#include -class SenseCapSolarBoard : public mesh::MainBoard { +class SenseCapSolarBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 359e5e9a1..d04de5a3e 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -2,8 +2,9 @@ #include #include +#include -class T1000eBoard : public mesh::MainBoard { +class T1000eBoard : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index cffa0aaaf..5920b809a 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -2,6 +2,7 @@ #include #include +#include // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 @@ -12,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM1Board : public mesh::MainBoard { +class ThinkNodeM1Board : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index f04b673f8..6797f6292 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -2,8 +2,9 @@ #include #include +#include -class WioTrackerL1Board : public mesh::MainBoard { +class WioTrackerL1Board : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index 823acbc7d..ffbc4e935 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef WIO_WM1110 @@ -10,7 +11,7 @@ #endif #define Serial Serial1 -class WioWM1110Board : public mesh::MainBoard { +class WioWM1110Board : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index f37660123..86be7aa44 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -2,10 +2,11 @@ #include #include +#include #ifdef XIAO_NRF52 -class XiaoNrf52Board : public mesh::MainBoard { +class XiaoNrf52Board : public NRF52Board { protected: uint8_t startup_reason; From d1b53ab228518b182d022a5b8e31ee4aa70d7ef0 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 13 Dec 2025 07:32:26 +0700 Subject: [PATCH 06/44] Fixed to call getMCUTemperature once. --- examples/simple_repeater/MyMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 39ecd1055..6ae6ac0a8 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -174,9 +174,9 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - float temperature = (float)board.getMCUTemperature(); + float temperature = board.getMCUTemperature(); if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN - telemetry.addTemperature(TELEM_CHANNEL_SELF, (float)board.getMCUTemperature()); // Built-in MCU Temperature + telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature } // query other sensors -- target specific From e07a1c3c588a6e9692ca017fef5259570186c262 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 17 Dec 2025 10:22:15 +0100 Subject: [PATCH 07/44] variants: IkokaNrf52Board: Use NRF52Board base class Signed-off-by: Frieder Schrempf --- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 9dfc3833e..58f9e3999 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -1,11 +1,12 @@ #pragma once -#include #include +#include +#include #ifdef IKOKA_NRF52 -class IkokaNrf52Board : public mesh::MainBoard { +class IkokaNrf52Board : public NRF52Board { protected: uint8_t startup_reason; From 7a39f58361906793b847186a3cd975ed35b1fd3b Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 14:31:55 +0100 Subject: [PATCH 08/44] Deduplicate reboot() for NRF52 boards The reboot() method is the same for all NRF52 boards. Use a shared implementation. Signed-off-by: Frieder Schrempf --- src/helpers/NRF52Board.h | 1 + variants/heltec_mesh_solar/MeshSolarBoard.h | 4 ---- variants/heltec_t114/T114Board.h | 4 ---- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 4 ---- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 4 ---- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 4 ---- variants/keepteen_lt1/KeepteenLT1Board.h | 4 ---- variants/lilygo_techo/TechoBoard.h | 4 ---- variants/lilygo_techo_lite/TechoBoard.h | 4 ---- variants/mesh_pocket/MeshPocket.h | 4 ---- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h | 5 ----- variants/nano_g2_ultra/nano-g2.h | 2 -- variants/promicro/PromicroBoard.h | 4 ---- variants/rak4631/RAK4631Board.h | 4 ---- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 4 ---- variants/sensecap_solar/SenseCapSolarBoard.h | 4 ---- variants/t1000-e/T1000eBoard.h | 4 ---- variants/thinknode_m1/ThinkNodeM1Board.h | 4 ---- variants/wio-tracker-l1/WioTrackerL1Board.h | 4 ---- variants/wio_wm1110/WioWM1110Board.h | 4 ---- variants/xiao_nrf52/XiaoNrf52Board.h | 4 ---- 21 files changed, 1 insertion(+), 79 deletions(-) diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 1a6f879f3..5158229d0 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -8,5 +8,6 @@ class NRF52Board : public mesh::MainBoard { public: float getMCUTemperature() override; + virtual void reboot() override { NVIC_SystemReset(); } }; #endif \ No newline at end of file diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 688a8e886..24c5a812e 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -37,9 +37,5 @@ class MeshSolarBoard : public NRF52Board { return "Heltec Mesh Solar"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index bd9287e28..e35afeb19 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -44,10 +44,6 @@ class T114Board : public NRF52Board { return "Heltec T114"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { #ifdef LED_PIN digitalWrite(LED_PIN, HIGH); diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 58f9e3999..87f859804 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -43,10 +43,6 @@ class IkokaNrf52Board : public NRF52Board { return "Ikoka Handheld E22 30dBm (Xiao_nrf52)"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index ac3069776..2d185365f 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -51,10 +51,6 @@ class IkokaNanoNRFBoard : public NRF52Board { return MANUFACTURER_STRING; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char *id, char reply[]) override; }; diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 8f817ff13..1a9b00c4c 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -51,10 +51,6 @@ class IkokaStickNRFBoard : public NRF52Board { return MANUFACTURER_STRING; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char *id, char reply[]) override; }; diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index e8c444ed5..2e720b72b 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -40,10 +40,6 @@ class KeepteenLT1Board : public NRF52Board { } #endif - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { sd_power_system_off(); } diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 91dfb5abb..316536aed 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -49,8 +49,4 @@ class TechoBoard : public NRF52Board { #endif sd_power_system_off(); } - - void reboot() override { - NVIC_SystemReset(); - } }; diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 79b5610c7..db800951f 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -49,8 +49,4 @@ class TechoBoard : public NRF52Board { #endif sd_power_system_off(); } - - void reboot() override { - NVIC_SystemReset(); - } }; diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 876e3810a..fd9c819eb 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -38,10 +38,6 @@ class HeltecMeshPocket : public NRF52Board { return "Heltec MeshPocket"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { sd_power_system_off(); } diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 18aa9e8a4..ba4964919 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -80,10 +80,5 @@ class MinewsemiME25LS01Board : public NRF52Board { } #endif - - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; \ No newline at end of file diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 7ed1c74b0..1e8dbce47 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -48,8 +48,6 @@ class NanoG2Ultra : public NRF52Board { const char *getManufacturerName() const override { return "Nano G2 Ultra"; } - void reboot() override { NVIC_SystemReset(); } - void powerOff() override { // put GPS chip to sleep digitalWrite(PIN_GPS_STANDBY, LOW); diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 916afefd5..551370104 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -75,10 +75,6 @@ class PromicroBoard : public NRF52Board { return 0; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { sd_power_system_off(); } diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index 2040bf41d..8922cc31a 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -55,9 +55,5 @@ class RAK4631Board : public NRF52Board { return "RAK 4631"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index fe554fe64..732edc69f 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -43,10 +43,6 @@ class RAKWismeshTagBoard : public NRF52Board { return "RAK WisMesh Tag"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; void powerOff() override { diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index 999c7783a..f14f2f1aa 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -35,9 +35,5 @@ class SenseCapSolarBoard : public NRF52Board { return "Seeed SenseCap Solar"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index d04de5a3e..02aa07330 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -93,9 +93,5 @@ class T1000eBoard : public NRF52Board { sd_power_system_off(); } - void reboot() override { - NVIC_SystemReset(); - } - // bool startOTAUpdate(const char* id, char reply[]) override; }; \ No newline at end of file diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index 5920b809a..b6eff743f 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -40,10 +40,6 @@ class ThinkNodeM1Board : public NRF52Board { return "Elecrow ThinkNode-M1"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { // turn off all leds, sd_power_system_off will not do this for us diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index 6797f6292..b51f56bdb 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -35,10 +35,6 @@ class WioTrackerL1Board : public NRF52Board { return "Seeed Wio Tracker L1"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { sd_power_system_off(); } diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index ffbc4e935..5d36834ba 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -41,10 +41,6 @@ class WioWM1110Board : public NRF52Board { return "Seeed Wio WM1110"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; void enableSensorPower(bool enable) { diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 86be7aa44..32dcc576a 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -43,10 +43,6 @@ class XiaoNrf52Board : public NRF52Board { return "Seeed Xiao-nrf52"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { // set led on and wait for button release before poweroff digitalWrite(PIN_LED, LOW); From a0cd596cf9e62b20fda27197d12cd29a838042e6 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 15:00:08 +0100 Subject: [PATCH 09/44] Use common NRF52 begin() and deduplicate() startup reason init Use a common begin() method that can be called from derived classes to contain the shared initialization code. Signed-off-by: Frieder Schrempf --- src/helpers/NRF52Board.h | 5 +++++ variants/heltec_mesh_solar/MeshSolarBoard.cpp | 3 +-- variants/heltec_mesh_solar/MeshSolarBoard.h | 4 ---- variants/heltec_t114/T114Board.cpp | 3 +-- variants/heltec_t114/T114Board.h | 4 ---- variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp | 3 +-- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 4 ---- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp | 3 +-- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 4 ---- variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp | 3 +-- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 4 ---- variants/keepteen_lt1/KeepteenLT1Board.cpp | 3 +-- variants/keepteen_lt1/KeepteenLT1Board.h | 3 --- variants/lilygo_techo/TechoBoard.cpp | 3 +-- variants/lilygo_techo/TechoBoard.h | 8 -------- variants/lilygo_techo_lite/TechoBoard.cpp | 3 +-- variants/lilygo_techo_lite/TechoBoard.h | 8 -------- variants/mesh_pocket/MeshPocket.cpp | 3 +-- variants/mesh_pocket/MeshPocket.h | 6 ------ variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp | 3 +-- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h | 3 --- variants/nano_g2_ultra/nano-g2.cpp | 3 +-- variants/nano_g2_ultra/nano-g2.h | 5 ----- variants/promicro/PromicroBoard.cpp | 3 +-- variants/promicro/PromicroBoard.h | 3 --- variants/rak4631/RAK4631Board.cpp | 3 +-- variants/rak4631/RAK4631Board.h | 4 ---- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 5 ++--- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 4 ---- variants/sensecap_solar/SenseCapSolarBoard.cpp | 3 +-- variants/sensecap_solar/SenseCapSolarBoard.h | 4 ---- variants/t1000-e/T1000eBoard.cpp | 3 +-- variants/t1000-e/T1000eBoard.h | 3 --- variants/thinknode_m1/ThinkNodeM1Board.cpp | 3 +-- variants/thinknode_m1/ThinkNodeM1Board.h | 7 ------- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 3 +-- variants/wio-tracker-l1/WioTrackerL1Board.h | 2 -- variants/wio_wm1110/WioWM1110Board.cpp | 2 +- variants/wio_wm1110/WioWM1110Board.h | 4 ---- variants/xiao_nrf52/XiaoNrf52Board.cpp | 3 +-- variants/xiao_nrf52/XiaoNrf52Board.h | 4 ---- 41 files changed, 26 insertions(+), 128 deletions(-) diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 5158229d0..c349a8e7a 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -6,7 +6,12 @@ #if defined(NRF52_PLATFORM) class NRF52Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + public: + virtual void begin() { startup_reason = BD_STARTUP_NORMAL; } + virtual uint8_t getStartupReason() const override { return startup_reason; } float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } }; diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.cpp b/variants/heltec_mesh_solar/MeshSolarBoard.cpp index 54929cd1e..ad5f1333e 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.cpp +++ b/variants/heltec_mesh_solar/MeshSolarBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) } void MeshSolarBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); meshSolarStart(); diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 24c5a812e..0ccbb1275 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -22,12 +22,8 @@ class MeshSolarBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } uint16_t getBattMilliVolts() override { return meshSolarGetBattVoltage(); diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index f46a1a84b..4730a49b0 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -19,8 +19,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; + NRF52Board::begin(); NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index e35afeb19..a040e37fb 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -10,12 +10,8 @@ #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range class T114Board : public NRF52Board { -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 { diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp index 44940b8ff..bf3f4a9e8 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void IkokaNrf52Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); // 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 diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 87f859804..25cf643c5 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -7,12 +7,8 @@ #ifdef IKOKA_NRF52 class IkokaNrf52Board : public NRF52Board { -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 { diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp index ee7996921..15507ad31 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void IkokaNanoNRFBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index 2d185365f..e6da3d115 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -7,12 +7,8 @@ #ifdef XIAO_NRF52 class IkokaNanoNRFBoard : public NRF52Board { -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 { diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp index 6b660383a..1adc14bd6 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void IkokaStickNRFBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 1a9b00c4c..78cb8a7fd 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -7,12 +7,8 @@ #ifdef XIAO_NRF52 class IkokaStickNRFBoard : public NRF52Board { -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 { diff --git a/variants/keepteen_lt1/KeepteenLT1Board.cpp b/variants/keepteen_lt1/KeepteenLT1Board.cpp index 46bff1fc2..a61909fe4 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.cpp +++ b/variants/keepteen_lt1/KeepteenLT1Board.cpp @@ -7,8 +7,7 @@ static BLEDfu bledfu; void KeepteenLT1Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index 2e720b72b..e98972ae2 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -6,14 +6,11 @@ class KeepteenLT1Board : public NRF52Board { 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 { diff --git a/variants/lilygo_techo/TechoBoard.cpp b/variants/lilygo_techo/TechoBoard.cpp index dee146880..e12596445 100644 --- a/variants/lilygo_techo/TechoBoard.cpp +++ b/variants/lilygo_techo/TechoBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void TechoBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Wire.begin(); diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 316536aed..b7650d007 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -14,19 +14,11 @@ #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) class TechoBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: - void begin(); uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char* id, char reply[]) override; - uint8_t getStartupReason() const override { - return startup_reason; - } - const char* getManufacturerName() const override { return "LilyGo T-Echo"; } diff --git a/variants/lilygo_techo_lite/TechoBoard.cpp b/variants/lilygo_techo_lite/TechoBoard.cpp index dee146880..e12596445 100644 --- a/variants/lilygo_techo_lite/TechoBoard.cpp +++ b/variants/lilygo_techo_lite/TechoBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void TechoBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Wire.begin(); diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index db800951f..c92bcfca4 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -14,19 +14,11 @@ #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) class TechoBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: - void begin(); uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char* id, char reply[]) override; - uint8_t getStartupReason() const override { - return startup_reason; - } - const char* getManufacturerName() const override { return "LilyGo T-Echo"; } diff --git a/variants/mesh_pocket/MeshPocket.cpp b/variants/mesh_pocket/MeshPocket.cpp index 0d0e8993c..0fa7d5f94 100644 --- a/variants/mesh_pocket/MeshPocket.cpp +++ b/variants/mesh_pocket/MeshPocket.cpp @@ -20,8 +20,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) } void HeltecMeshPocket::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Serial.begin(115200); pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index fd9c819eb..0f4d2c8b5 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -10,14 +10,8 @@ #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range class HeltecMeshPocket : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } - - uint16_t getBattMilliVolts() override { int adcvalue = 0; diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp index 561ed5049..c38f71e45 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp @@ -5,8 +5,7 @@ #include void MinewsemiME25LS01Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index ba4964919..34612e93e 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -23,7 +23,6 @@ class MinewsemiME25LS01Board : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: @@ -42,8 +41,6 @@ class MinewsemiME25LS01Board : public NRF52Board { return (ADC_MULTIPLIER * raw); } - uint8_t getStartupReason() const override { return startup_reason; } - const char* getManufacturerName() const override { return "Minewsemi"; } diff --git a/variants/nano_g2_ultra/nano-g2.cpp b/variants/nano_g2_ultra/nano-g2.cpp index 9a2782871..6a749aab6 100644 --- a/variants/nano_g2_ultra/nano-g2.cpp +++ b/variants/nano_g2_ultra/nano-g2.cpp @@ -24,8 +24,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) void NanoG2Ultra::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); // set user button pinMode(PIN_BUTTON1, INPUT); diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 1e8dbce47..6961fc86d 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -36,16 +36,11 @@ #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) class NanoG2Ultra : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char *id, char reply[]) override; - uint8_t getStartupReason() const override { return startup_reason; } - const char *getManufacturerName() const override { return "Nano G2 Ultra"; } void powerOff() override { diff --git a/variants/promicro/PromicroBoard.cpp b/variants/promicro/PromicroBoard.cpp index b923e16e5..4ac720721 100644 --- a/variants/promicro/PromicroBoard.cpp +++ b/variants/promicro/PromicroBoard.cpp @@ -7,8 +7,7 @@ static BLEDfu bledfu; void PromicroBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 551370104..ba0021346 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -22,15 +22,12 @@ class PromicroBoard : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; float adc_mult = ADC_MULTIPLIER; public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } - #define BATTERY_SAMPLES 8 uint16_t getBattMilliVolts() override { diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 97a966022..1a37db156 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -19,8 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void RAK4631Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); pinMode(PIN_VBAT_READ, INPUT); #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index 8922cc31a..394b05fb0 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -30,12 +30,8 @@ #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) class RAK4631Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #define BATTERY_SAMPLES 8 diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 68638f0dd..11b9f0a51 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -19,9 +19,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; - + NRF52Board::begin(); + // Enable DC/DC converter for improved power efficiency uint8_t sd_enabled = 0; sd_softdevice_is_enabled(&sd_enabled); diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index 732edc69f..032b00af4 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -9,12 +9,8 @@ #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) class RAKWismeshTagBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) && defined(LED_STATE_ON) void onBeforeTransmit() override { diff --git a/variants/sensecap_solar/SenseCapSolarBoard.cpp b/variants/sensecap_solar/SenseCapSolarBoard.cpp index d6c044d1d..1493d22a1 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.cpp +++ b/variants/sensecap_solar/SenseCapSolarBoard.cpp @@ -19,8 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void SenseCapSolarBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index f14f2f1aa..eab08163b 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -5,12 +5,8 @@ #include class SenseCapSolarBoard : public NRF52Board { -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 { diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index a41abd92e..27432b1b4 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -5,8 +5,7 @@ #include void T1000eBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; // Enable DC/DC converter for improved power efficiency diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 02aa07330..c16d8ccd7 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -6,7 +6,6 @@ class T1000eBoard : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: @@ -34,8 +33,6 @@ class T1000eBoard : public NRF52Board { #endif } - uint8_t getStartupReason() const override { return startup_reason; } - const char* getManufacturerName() const override { return "Seeed Tracker T1000-e"; } diff --git a/variants/thinknode_m1/ThinkNodeM1Board.cpp b/variants/thinknode_m1/ThinkNodeM1Board.cpp index 12dd73624..bfec5f3fb 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.cpp +++ b/variants/thinknode_m1/ThinkNodeM1Board.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void ThinkNodeM1Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Wire.begin(); diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index b6eff743f..7e961f003 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -14,19 +14,12 @@ #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) class ThinkNodeM1Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char* id, char reply[]) override; - uint8_t getStartupReason() const override { - return startup_reason; - } - #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index 34d9a8745..d07df83f9 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -19,8 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void WioTrackerL1Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; // Enable DC/DC converter for improved power efficiency diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index b51f56bdb..e1930ff79 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -6,12 +6,10 @@ class WioTrackerL1Board : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index 153d476c4..cb54609d4 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -21,7 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void WioWM1110Board::begin() { - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); // Enable DC/DC converter for improved power efficiency uint8_t sd_enabled = 0; diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index 5d36834ba..9b4e153b4 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -12,12 +12,8 @@ #define Serial Serial1 class WioWM1110Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(LED_GREEN) void onBeforeTransmit() override { diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index f847c6543..34c157677 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -21,8 +21,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; + NRF52Board::begin(); // Enable DC/DC converter for improved power efficiency uint8_t sd_enabled = 0; diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 32dcc576a..1f1490cf2 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -7,12 +7,8 @@ #ifdef XIAO_NRF52 class XiaoNrf52Board : public NRF52Board { -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 { From b16660cce3414c52de83a4b5ced0902461b752cb Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 17:05:33 +0100 Subject: [PATCH 10/44] Deduplicate DC/DC regulator enable for NRF52 boards Some NRF52 boards are able to use the internal power-efficient DC/DC regulator. Add a new class that can be inherited by board classes to enable this feature and reduce the power consumption. Signed-off-by: Frieder Schrempf --- src/helpers/NRF52Board.cpp | 17 +++++++++++++++++ src/helpers/NRF52Board.h | 14 +++++++++++++- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 11 +---------- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 2 +- variants/t1000-e/T1000eBoard.cpp | 11 +---------- variants/t1000-e/T1000eBoard.h | 2 +- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 11 +---------- variants/wio-tracker-l1/WioTrackerL1Board.h | 2 +- variants/wio_wm1110/WioWM1110Board.cpp | 11 +---------- variants/wio_wm1110/WioWM1110Board.h | 2 +- variants/xiao_nrf52/XiaoNrf52Board.cpp | 11 +---------- variants/xiao_nrf52/XiaoNrf52Board.h | 2 +- 12 files changed, 40 insertions(+), 56 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index ee50fe4c6..93dfb2881 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -1,6 +1,23 @@ #if defined(NRF52_PLATFORM) #include "NRF52Board.h" +void NRF52Board::begin() { + startup_reason = BD_STARTUP_NORMAL; +} + +void NRF52BoardDCDC::begin() { + NRF52Board::begin(); + + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } +} + // Temperature from NRF52 MCU float NRF52Board::getMCUTemperature() { NRF_TEMP->TASKS_START = 1; // Start temperature measurement diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index c349a8e7a..588c190a1 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -10,9 +10,21 @@ class NRF52Board : public mesh::MainBoard { uint8_t startup_reason; public: - virtual void begin() { startup_reason = BD_STARTUP_NORMAL; } + virtual void begin(); virtual uint8_t getStartupReason() const override { return startup_reason; } float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } }; + +/* + * The NRF52 has an internal DC/DC regulator that allows increased efficiency + * compared to the LDO regulator. For being able to use it, the module/board + * needs to have the required inductors and and capacitors populated. If the + * hardware requirements are met, this subclass can be used to enable the DC/DC + * regulator. + */ +class NRF52BoardDCDC : virtual public NRF52Board { +public: + virtual void begin() override; +}; #endif \ No newline at end of file diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 11b9f0a51..88d09249b 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -19,16 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void RAKWismeshTagBoard::begin() { - NRF52Board::begin(); - - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT_READ, INPUT); pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index 032b00af4..d62dee854 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -8,7 +8,7 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAKWismeshTagBoard : public NRF52Board { +class RAKWismeshTagBoard : public NRF52BoardDCDC { public: void begin(); diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index 27432b1b4..9211b4c64 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -5,18 +5,9 @@ #include void T1000eBoard::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); btn_prev_state = HIGH; - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } - #ifdef BUTTON_PIN pinMode(BATTERY_PIN, INPUT); pinMode(BUTTON_PIN, INPUT); diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index c16d8ccd7..32f54bb38 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -4,7 +4,7 @@ #include #include -class T1000eBoard : public NRF52Board { +class T1000eBoard : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index d07df83f9..fbf64f77a 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -19,18 +19,9 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void WioTrackerL1Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); btn_prev_state = HIGH; - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } - pinMode(PIN_VBAT_READ, INPUT); // VBAT ADC input // Set all button pins to INPUT_PULLUP pinMode(PIN_BUTTON1, INPUT_PULLUP); diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index e1930ff79..7e82e4477 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -4,7 +4,7 @@ #include #include -class WioTrackerL1Board : public NRF52Board { +class WioTrackerL1Board : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index cb54609d4..d0ab97858 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -21,16 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void WioWM1110Board::begin() { - NRF52Board::begin(); - - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } + NRF52BoardDCDC::begin(); pinMode(BATTERY_PIN, INPUT); pinMode(LED_GREEN, OUTPUT); diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index 9b4e153b4..697028b06 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -11,7 +11,7 @@ #endif #define Serial Serial1 -class WioWM1110Board : public NRF52Board { +class WioWM1110Board : public NRF52BoardDCDC { public: void begin(); diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 34c157677..e61f38066 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -21,16 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void XiaoNrf52Board::begin() { - NRF52Board::begin(); - - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 1f1490cf2..073254939 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -6,7 +6,7 @@ #ifdef XIAO_NRF52 -class XiaoNrf52Board : public NRF52Board { +class XiaoNrf52Board : public NRF52BoardDCDC { public: void begin(); From c69664703a7204aa17a817eab757d6761f13a70f Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 19:56:00 +0100 Subject: [PATCH 11/44] Deduplicate NRF52 startOTAUpdate() The startOTAUpdate() is the same for all NRF52 boards. Use a common implementation for all boards that currently have a specific implementation. The following boards currently have an empty startOTAUpdate() for whatever reasons and therefore are not inheriting NRF52BoardOTA to keep the same state: Nano G2 Ultra, Seeed SenseCAP T1000-E, Wio WM1110. Signed-off-by: Frieder Schrempf --- src/helpers/NRF52Board.cpp | 64 ++++++++++++++++++ src/helpers/NRF52Board.h | 9 +++ variants/heltec_mesh_solar/MeshSolarBoard.cpp | 62 +---------------- variants/heltec_mesh_solar/MeshSolarBoard.h | 6 +- variants/heltec_t114/T114Board.cpp | 60 +---------------- variants/heltec_t114/T114Board.h | 5 +- .../ikoka_handheld_nrf/IkokaNrf52Board.cpp | 59 ---------------- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 5 +- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp | 60 +---------------- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 5 +- .../ikoka_stick_nrf/IkokaStickNRFBoard.cpp | 60 +---------------- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 5 +- variants/keepteen_lt1/KeepteenLT1Board.cpp | 61 +---------------- variants/keepteen_lt1/KeepteenLT1Board.h | 5 +- variants/lilygo_techo/TechoBoard.cpp | 62 +---------------- variants/lilygo_techo/TechoBoard.h | 4 +- variants/lilygo_techo_lite/TechoBoard.cpp | 62 +---------------- variants/lilygo_techo_lite/TechoBoard.h | 4 +- variants/mesh_pocket/MeshPocket.cpp | 61 +---------------- variants/mesh_pocket/MeshPocket.h | 5 +- .../MinewsemiME25LS01Board.cpp | 61 +---------------- .../MinewsemiME25LS01Board.h | 6 +- variants/promicro/PromicroBoard.cpp | 61 +---------------- variants/promicro/PromicroBoard.h | 5 +- variants/rak4631/RAK4631Board.cpp | 67 +------------------ variants/rak4631/RAK4631Board.h | 5 +- .../rak_wismesh_tag/RAKWismeshTagBoard.cpp | 67 +------------------ variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 5 +- .../sensecap_solar/SenseCapSolarBoard.cpp | 63 +---------------- variants/sensecap_solar/SenseCapSolarBoard.h | 5 +- variants/t1000-e/T1000eBoard.cpp | 65 +----------------- variants/thinknode_m1/ThinkNodeM1Board.cpp | 62 +---------------- variants/thinknode_m1/ThinkNodeM1Board.h | 5 +- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 65 +----------------- variants/wio-tracker-l1/WioTrackerL1Board.h | 5 +- variants/xiao_nrf52/XiaoNrf52Board.cpp | 59 ---------------- variants/xiao_nrf52/XiaoNrf52Board.h | 5 +- 37 files changed, 132 insertions(+), 1143 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 93dfb2881..c0d58314e 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -1,6 +1,22 @@ #if defined(NRF52_PLATFORM) #include "NRF52Board.h" +#include + +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 NRF52Board::begin() { startup_reason = BD_STARTUP_NORMAL; } @@ -37,4 +53,52 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } + +bool NRF52BoardOTA::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(ota_name); + + 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 + + uint8_t mac_addr[6]; + memset(mac_addr, 0, sizeof(mac_addr)); + Bluefruit.getAddr(mac_addr); + sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", mac_addr[5], mac_addr[4], mac_addr[3], + mac_addr[2], mac_addr[1], mac_addr[0]); + + return true; +} #endif diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 588c190a1..c5f32a204 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -27,4 +27,13 @@ class NRF52BoardDCDC : virtual public NRF52Board { public: virtual void begin() override; }; + +class NRF52BoardOTA : virtual public NRF52Board { +private: + char *ota_name; + +public: + NRF52BoardOTA(char *name) : ota_name(name) {} + virtual bool startOTAUpdate(const char *id, char reply[]) override; +}; #endif \ No newline at end of file diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.cpp b/variants/heltec_mesh_solar/MeshSolarBoard.cpp index ad5f1333e..bc955fb53 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.cpp +++ b/variants/heltec_mesh_solar/MeshSolarBoard.cpp @@ -1,24 +1,7 @@ #include -#include "MeshSolarBoard.h" - -#include #include -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"); -} +#include "MeshSolarBoard.h" void MeshSolarBoard::begin() { NRF52Board::begin(); @@ -31,46 +14,3 @@ void MeshSolarBoard::begin() { Wire.begin(); } - -bool MeshSolarBoard::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("MESH_SOLAR_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/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 0ccbb1275..69437c877 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -20,9 +20,9 @@ #define SX126X_DIO2_AS_RF_SWITCH true #define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -class MeshSolarBoard : public NRF52Board { +class MeshSolarBoard : public NRF52BoardOTA { public: + MeshSolarBoard() : NRF52BoardOTA("MESH_SOLAR_OTA") {} void begin(); uint16_t getBattMilliVolts() override { @@ -32,6 +32,4 @@ class MeshSolarBoard : public NRF52Board { const char* getManufacturerName() const override { return "Heltec Mesh Solar"; } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index 4730a49b0..4995e7de1 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -2,21 +2,6 @@ #include #include -#include - -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 T114Board::begin() { NRF52Board::begin(); @@ -38,47 +23,4 @@ void T114Board::begin() { pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up -} - -bool T114Board::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("T114_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; -} +} \ No newline at end of file diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index a040e37fb..74e26455c 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -9,8 +9,9 @@ #define PIN_BAT_CTL 6 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class T114Board : public NRF52Board { +class T114Board : public NRF52BoardOTA { public: + T114Board() : NRF52BoardOTA("T114_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -50,6 +51,4 @@ class T114Board : public NRF52Board { #endif sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp index bf3f4a9e8..f1d9f17d3 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp @@ -2,24 +2,9 @@ #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() { NRF52Board::begin(); @@ -52,48 +37,4 @@ void IkokaNrf52Board::begin() { 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 index 25cf643c5..acb58b3ce 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -6,8 +6,9 @@ #ifdef IKOKA_NRF52 -class IkokaNrf52Board : public NRF52Board { +class IkokaNrf52Board : public NRF52BoardOTA { public: + IkokaNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -38,8 +39,6 @@ class IkokaNrf52Board : public NRF52Board { const char* getManufacturerName() const override { return "Ikoka Handheld E22 30dBm (Xiao_nrf52)"; } - - bool startOTAUpdate(const char* id, char reply[]) override; }; #endif diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp index 15507ad31..b963b2f4b 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp @@ -1,24 +1,9 @@ #ifdef XIAO_NRF52 #include -#include "IkokaNanoNRFBoard.h" - -#include #include -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"); -} +#include "IkokaNanoNRFBoard.h" void IkokaNanoNRFBoard::begin() { NRF52Board::begin(); @@ -47,47 +32,4 @@ void IkokaNanoNRFBoard::begin() { delay(10); // give sx1262 some time to power up } -bool IkokaNanoNRFBoard::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_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index e6da3d115..c7e96921e 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -6,8 +6,9 @@ #ifdef XIAO_NRF52 -class IkokaNanoNRFBoard : public NRF52Board { +class IkokaNanoNRFBoard : public NRF52BoardOTA { public: + IkokaNanoNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -46,8 +47,6 @@ class IkokaNanoNRFBoard : public NRF52Board { const char *getManufacturerName() const override { return MANUFACTURER_STRING; } - - bool startOTAUpdate(const char *id, char reply[]) override; }; #endif diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp index 1adc14bd6..1c416039d 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp @@ -1,24 +1,9 @@ #ifdef XIAO_NRF52 #include -#include "IkokaStickNRFBoard.h" - -#include #include -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"); -} +#include "IkokaStickNRFBoard.h" void IkokaStickNRFBoard::begin() { NRF52Board::begin(); @@ -47,47 +32,4 @@ void IkokaStickNRFBoard::begin() { delay(10); // give sx1262 some time to power up } -bool IkokaStickNRFBoard::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_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 78cb8a7fd..00a26029c 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -6,8 +6,9 @@ #ifdef XIAO_NRF52 -class IkokaStickNRFBoard : public NRF52Board { +class IkokaStickNRFBoard : public NRF52BoardOTA { public: + IkokaStickNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -46,8 +47,6 @@ class IkokaStickNRFBoard : public NRF52Board { const char *getManufacturerName() const override { return MANUFACTURER_STRING; } - - bool startOTAUpdate(const char *id, char reply[]) override; }; #endif diff --git a/variants/keepteen_lt1/KeepteenLT1Board.cpp b/variants/keepteen_lt1/KeepteenLT1Board.cpp index a61909fe4..2f1fa5f9a 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.cpp +++ b/variants/keepteen_lt1/KeepteenLT1Board.cpp @@ -1,10 +1,7 @@ #include -#include "KeepteenLT1Board.h" - -#include #include -static BLEDfu bledfu; +#include "KeepteenLT1Board.h" void KeepteenLT1Board::begin() { NRF52Board::begin(); @@ -17,58 +14,4 @@ void KeepteenLT1Board::begin() { #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; -} +} \ No newline at end of file diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index e98972ae2..a9844c90b 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -4,11 +4,12 @@ #include #include -class KeepteenLT1Board : public NRF52Board { +class KeepteenLT1Board : public NRF52BoardOTA { protected: uint8_t btn_prev_state; public: + KeepteenLT1Board() : NRF52BoardOTA("KeepteenLT1_OTA") {} void begin(); #define BATTERY_SAMPLES 8 @@ -40,6 +41,4 @@ class KeepteenLT1Board : public NRF52Board { void powerOff() override { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/lilygo_techo/TechoBoard.cpp b/variants/lilygo_techo/TechoBoard.cpp index e12596445..81d3d0c9a 100644 --- a/variants/lilygo_techo/TechoBoard.cpp +++ b/variants/lilygo_techo/TechoBoard.cpp @@ -1,24 +1,9 @@ #include -#include "TechoBoard.h" - -#ifdef LILYGO_TECHO - -#include #include -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; +#include "TechoBoard.h" - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#ifdef LILYGO_TECHO void TechoBoard::begin() { NRF52Board::begin(); @@ -43,47 +28,4 @@ uint16_t TechoBoard::getBattMilliVolts() { // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool TechoBoard::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("TECHO_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/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index b7650d007..c9bdc229b 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -13,11 +13,11 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52Board { +class TechoBoard : public NRF52BoardOTA { public: + TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char* id, char reply[]) override; const char* getManufacturerName() const override { return "LilyGo T-Echo"; diff --git a/variants/lilygo_techo_lite/TechoBoard.cpp b/variants/lilygo_techo_lite/TechoBoard.cpp index e12596445..81d3d0c9a 100644 --- a/variants/lilygo_techo_lite/TechoBoard.cpp +++ b/variants/lilygo_techo_lite/TechoBoard.cpp @@ -1,24 +1,9 @@ #include -#include "TechoBoard.h" - -#ifdef LILYGO_TECHO - -#include #include -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; +#include "TechoBoard.h" - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#ifdef LILYGO_TECHO void TechoBoard::begin() { NRF52Board::begin(); @@ -43,47 +28,4 @@ uint16_t TechoBoard::getBattMilliVolts() { // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool TechoBoard::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("TECHO_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/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index c92bcfca4..8e6974bd3 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -13,11 +13,11 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52Board { +class TechoBoard : public NRF52BoardOTA { public: + TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char* id, char reply[]) override; const char* getManufacturerName() const override { return "LilyGo T-Echo"; diff --git a/variants/mesh_pocket/MeshPocket.cpp b/variants/mesh_pocket/MeshPocket.cpp index 0fa7d5f94..0c53121f2 100644 --- a/variants/mesh_pocket/MeshPocket.cpp +++ b/variants/mesh_pocket/MeshPocket.cpp @@ -1,23 +1,7 @@ #include -#include "MeshPocket.h" -#include #include -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"); -} +#include "MeshPocket.h" void HeltecMeshPocket::begin() { NRF52Board::begin(); @@ -26,46 +10,3 @@ void HeltecMeshPocket::begin() { pinMode(PIN_USER_BTN, INPUT); } - -bool HeltecMeshPocket::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("MESH_POCKET_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/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 0f4d2c8b5..db65c99b1 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -9,8 +9,9 @@ #define PIN_BAT_CTL 34 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class HeltecMeshPocket : public NRF52Board { +class HeltecMeshPocket : public NRF52BoardOTA { public: + HeltecMeshPocket() : NRF52BoardOTA("MESH_POCKET_OTA") {} void begin(); uint16_t getBattMilliVolts() override { @@ -35,6 +36,4 @@ class HeltecMeshPocket : public NRF52Board { void powerOff() override { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp index c38f71e45..0267185c9 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp @@ -1,8 +1,7 @@ #include -#include "MinewsemiME25LS01Board.h" #include -#include +#include "MinewsemiME25LS01Board.h" void MinewsemiME25LS01Board::begin() { NRF52Board::begin(); @@ -27,62 +26,4 @@ void MinewsemiME25LS01Board::begin() { #endif delay(10); // give sx1262 some time to power up -} - -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"); -} - - -bool MinewsemiME25LS01Board::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("Minewsemi_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; } \ No newline at end of file diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 34612e93e..805f9dfa8 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -20,12 +20,12 @@ #define PIN_VBAT_READ BATTERY_PIN #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking - -class MinewsemiME25LS01Board : public NRF52Board { +class MinewsemiME25LS01Board : public NRF52BoardOTA { protected: uint8_t btn_prev_state; public: + MinewsemiME25LS01Board() : NRF52BoardOTA("Minewsemi_OTA") {} void begin(); #define BATTERY_SAMPLES 8 @@ -76,6 +76,4 @@ class MinewsemiME25LS01Board : public NRF52Board { digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off } #endif - - bool startOTAUpdate(const char* id, char reply[]) override; }; \ No newline at end of file diff --git a/variants/promicro/PromicroBoard.cpp b/variants/promicro/PromicroBoard.cpp index 4ac720721..7011521b8 100644 --- a/variants/promicro/PromicroBoard.cpp +++ b/variants/promicro/PromicroBoard.cpp @@ -1,10 +1,7 @@ #include -#include "PromicroBoard.h" - -#include #include -static BLEDfu bledfu; +#include "PromicroBoard.h" void PromicroBoard::begin() { NRF52Board::begin(); @@ -25,58 +22,4 @@ void PromicroBoard::begin() { pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up -} - -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 PromicroBoard::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("ProMicro_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; -} +} \ No newline at end of file diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index ba0021346..c23ed1c99 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -20,12 +20,13 @@ #define PIN_VBAT_READ 17 #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class PromicroBoard : public NRF52Board { +class PromicroBoard : public NRF52BoardOTA { protected: uint8_t btn_prev_state; float adc_mult = ADC_MULTIPLIER; public: + PromicroBoard() : NRF52BoardOTA("ProMicro_OTA") {} void begin(); #define BATTERY_SAMPLES 8 @@ -75,6 +76,4 @@ class PromicroBoard : public NRF52Board { void powerOff() override { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 1a37db156..cb7d19308 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -1,22 +1,7 @@ #include -#include "RAK4631Board.h" - -#include #include -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"); -} +#include "RAK4631Board.h" void RAK4631Board::begin() { NRF52Board::begin(); @@ -38,52 +23,4 @@ void RAK4631Board::begin() { pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up -} - -bool RAK4631Board::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("RAK4631_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 - - uint8_t mac_addr[6]; - memset(mac_addr, 0, sizeof(mac_addr)); - Bluefruit.getAddr(mac_addr); - sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", - mac_addr[5], mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]); - - return true; -} +} \ No newline at end of file diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index 394b05fb0..b329cdeb8 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -29,8 +29,9 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAK4631Board : public NRF52Board { +class RAK4631Board : public NRF52BoardOTA { public: + RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {} void begin(); #define BATTERY_SAMPLES 8 @@ -50,6 +51,4 @@ class RAK4631Board : public NRF52Board { const char* getManufacturerName() const override { return "RAK 4631"; } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 88d09249b..7eab4bc9e 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -1,22 +1,7 @@ #include -#include "RAKWismeshTagBoard.h" - -#include #include -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"); -} +#include "RAKWismeshTagBoard.h" void RAKWismeshTagBoard::begin() { NRF52BoardDCDC::begin(); @@ -30,52 +15,4 @@ void RAKWismeshTagBoard::begin() { pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up -} - -bool RAKWismeshTagBoard::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("WISMESHTAG_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 - - uint8_t mac_addr[6]; - memset(mac_addr, 0, sizeof(mac_addr)); - Bluefruit.getAddr(mac_addr); - sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", - mac_addr[5], mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]); - - return true; -} +} \ No newline at end of file diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index d62dee854..9aa457d24 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -8,8 +8,9 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAKWismeshTagBoard : public NRF52BoardDCDC { +class RAKWismeshTagBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: + RAKWismeshTagBoard() : NRF52BoardOTA("WISMESHTAG_OTA") {} void begin(); #if defined(P_LORA_TX_LED) && defined(LED_STATE_ON) @@ -39,8 +40,6 @@ class RAKWismeshTagBoard : public NRF52BoardDCDC { return "RAK WisMesh Tag"; } - bool startOTAUpdate(const char* id, char reply[]) override; - void powerOff() override { #ifdef BUZZER_EN digitalWrite(BUZZER_EN, LOW); diff --git a/variants/sensecap_solar/SenseCapSolarBoard.cpp b/variants/sensecap_solar/SenseCapSolarBoard.cpp index 1493d22a1..c08830359 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.cpp +++ b/variants/sensecap_solar/SenseCapSolarBoard.cpp @@ -1,22 +1,7 @@ #include -#include "SenseCapSolarBoard.h" - -#include #include -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"); -} +#include "SenseCapSolarBoard.h" void SenseCapSolarBoard::begin() { NRF52Board::begin(); @@ -33,48 +18,4 @@ void SenseCapSolarBoard::begin() { #endif delay(10); // give sx1262 some time to power up -} - -bool SenseCapSolarBoard::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("SENSECAP_SOLAR_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; -} +} \ No newline at end of file diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index eab08163b..dfe007bf7 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -4,8 +4,9 @@ #include #include -class SenseCapSolarBoard : public NRF52Board { +class SenseCapSolarBoard : public NRF52BoardOTA { public: + SenseCapSolarBoard() : NRF52BoardOTA("SENSECAP_SOLAR_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -30,6 +31,4 @@ class SenseCapSolarBoard : public NRF52Board { const char* getManufacturerName() const override { return "Seeed SenseCap Solar"; } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index 9211b4c64..0d390f798 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -1,8 +1,7 @@ #include -#include "T1000eBoard.h" #include -#include +#include "T1000eBoard.h" void T1000eBoard::begin() { NRF52BoardDCDC::begin(); @@ -21,64 +20,4 @@ void T1000eBoard::begin() { Wire.begin(); delay(10); // give sx1262 some time to power up -} - -#if 0 -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"); -} - - -bool TrackerT1000eBoard::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("T1000E_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 \ No newline at end of file +} \ No newline at end of file diff --git a/variants/thinknode_m1/ThinkNodeM1Board.cpp b/variants/thinknode_m1/ThinkNodeM1Board.cpp index bfec5f3fb..45449bafc 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.cpp +++ b/variants/thinknode_m1/ThinkNodeM1Board.cpp @@ -1,24 +1,9 @@ -#include "ThinkNodeM1Board.h" #include - -#ifdef THINKNODE_M1 - #include -#include - -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; +#include "ThinkNodeM1Board.h" - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#ifdef THINKNODE_M1 void ThinkNodeM1Board::begin() { NRF52Board::begin(); @@ -48,47 +33,4 @@ uint16_t ThinkNodeM1Board::getBattMilliVolts() { // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool ThinkNodeM1Board::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("THINKNODE_M1_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/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index 7e961f003..500a02658 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -13,12 +13,11 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM1Board : public NRF52Board { +class ThinkNodeM1Board : public NRF52BoardOTA { public: - + ThinkNodeM1Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char* id, char reply[]) override; #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index fbf64f77a..4153714e9 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -1,22 +1,7 @@ #include -#include "WioTrackerL1Board.h" - -#include #include -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"); -} +#include "WioTrackerL1Board.h" void WioTrackerL1Board::begin() { NRF52BoardDCDC::begin(); @@ -45,51 +30,3 @@ void WioTrackerL1Board::begin() { delay(10); // give sx1262 some time to power up } - -bool WioTrackerL1Board::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("WioTrackerL1 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 - - uint8_t mac_addr[6]; - memset(mac_addr, 0, sizeof(mac_addr)); - Bluefruit.getAddr(mac_addr); - sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", - mac_addr[5], mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]); - - return true; -} diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index 7e82e4477..bfd87d396 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -4,11 +4,12 @@ #include #include -class WioTrackerL1Board : public NRF52BoardDCDC { +class WioTrackerL1Board : public NRF52BoardDCDC, public NRF52BoardOTA { protected: uint8_t btn_prev_state; public: + WioTrackerL1Board() : NRF52BoardOTA("WioTrackerL1 OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -36,6 +37,4 @@ class WioTrackerL1Board : public NRF52BoardDCDC { void powerOff() override { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index e61f38066..b7b60dc63 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -2,24 +2,9 @@ #include #include -#include #include "XiaoNrf52Board.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 XiaoNrf52Board::begin() { NRF52BoardDCDC::begin(); @@ -47,48 +32,4 @@ void XiaoNrf52Board::begin() { delay(10); // give sx1262 some time to power up } -bool XiaoNrf52Board::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 \ No newline at end of file diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 073254939..1c46dfeee 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -6,8 +6,9 @@ #ifdef XIAO_NRF52 -class XiaoNrf52Board : public NRF52BoardDCDC { +class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: + XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -56,8 +57,6 @@ class XiaoNrf52Board : public NRF52BoardDCDC { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; #endif \ No newline at end of file From 680fefe734ba7b0c39f1d76b96fb31e18f5b7aec Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 17 Dec 2025 10:35:47 +0100 Subject: [PATCH 12/44] NRF52Board.h: Mark getMCUTemperature() as virtual The function in the derived class is virtual per definition. Mark it to make this clearer to the reader. Signed-off-by: Frieder Schrempf --- src/helpers/NRF52Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index c5f32a204..0d6c0a431 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -12,7 +12,7 @@ class NRF52Board : public mesh::MainBoard { public: virtual void begin(); virtual uint8_t getStartupReason() const override { return startup_reason; } - float getMCUTemperature() override; + virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } }; From f5a6a8c1841c8f0d27359bcf6516c6533e8d0b43 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 20:58:11 +0100 Subject: [PATCH 13/44] variants: RAK4631: Enable DC/DC regulator to reduce power consumption The RAK4631/RAK4630 module are able to use the DC/DC converter. Enable it to reduce power consumption. Signed-off-by: Frieder Schrempf --- variants/rak4631/RAK4631Board.cpp | 2 +- variants/rak4631/RAK4631Board.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index cb7d19308..65c54711d 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -4,7 +4,7 @@ #include "RAK4631Board.h" void RAK4631Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT_READ, INPUT); #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index b329cdeb8..a181256b0 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -29,7 +29,7 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAK4631Board : public NRF52BoardOTA { +class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {} void begin(); From bb049427910c4adb5ba2f938e745804c9fe94a03 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:01:38 +0100 Subject: [PATCH 14/44] queue throttling + slave latency and minor refactor --- src/helpers/nrf52/SerialBLEInterface.cpp | 90 ++++++++++++++++-------- src/helpers/nrf52/SerialBLEInterface.h | 2 + 2 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index b4811a207..eb1e90bb7 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -4,7 +4,23 @@ #include "ble_gap.h" #include "ble_hci.h" +// Magic numbers came from actual testing #define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds +#define BLE_RETRY_THROTTLE_MS 250 // Throttle retries to 250ms when queue buildup detected + +// Connection parameters (units: interval=1.25ms, timeout=10ms) +#define BLE_MIN_CONN_INTERVAL 12 // 15ms +#define BLE_MAX_CONN_INTERVAL 24 // 30ms +#define BLE_SLAVE_LATENCY 4 +#define BLE_CONN_SUP_TIMEOUT 200 // 2000ms + +// Advertising parameters +#define BLE_ADV_INTERVAL_MIN 32 // 20ms (units: 0.625ms) +#define BLE_ADV_INTERVAL_MAX 244 // 152.5ms (units: 0.625ms) +#define BLE_ADV_FAST_TIMEOUT 30 // seconds + +// RX drain buffer size for overflow protection +#define BLE_RX_DRAIN_BUF_SIZE 32 static SerialBLEInterface* instance = nullptr; @@ -38,14 +54,18 @@ void SerialBLEInterface::onSecured(uint16_t connection_handle) { // Apple: "The product will not read or use the parameters in the Peripheral Preferred Connection Parameters characteristic." // So we explicitly set it here to make Android & Apple match ble_gap_conn_params_t conn_params; - conn_params.min_conn_interval = 12; // 15ms - conn_params.max_conn_interval = 24; // 30ms - conn_params.slave_latency = 0; - conn_params.conn_sup_timeout = 200; // 2000ms + conn_params.min_conn_interval = BLE_MIN_CONN_INTERVAL; + conn_params.max_conn_interval = BLE_MAX_CONN_INTERVAL; + conn_params.slave_latency = BLE_SLAVE_LATENCY; + conn_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT; uint32_t err_code = sd_ble_gap_conn_param_update(connection_handle, &conn_params); if (err_code == NRF_SUCCESS) { - BLE_DEBUG_PRINTLN("Connection parameter update requested: 15-30ms interval, 2s timeout"); + BLE_DEBUG_PRINTLN("Connection parameter update requested: %u-%ums interval, latency=%u, %ums timeout", + conn_params.min_conn_interval * 5 / 4, // convert to ms (1.25ms units) + conn_params.max_conn_interval * 5 / 4, + conn_params.slave_latency, + conn_params.conn_sup_timeout * 10); // convert to ms (10ms units) } else { BLE_DEBUG_PRINTLN("Failed to request connection parameter update: %lu", err_code); } @@ -116,14 +136,18 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { // Connection interval units: 1.25ms, supervision timeout units: 10ms ble_gap_conn_params_t ppcp_params; - ppcp_params.min_conn_interval = 12; // 15ms - ppcp_params.max_conn_interval = 24; // 30ms - ppcp_params.slave_latency = 0; - ppcp_params.conn_sup_timeout = 200; // 2000ms + ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL; + ppcp_params.max_conn_interval = BLE_MAX_CONN_INTERVAL; + ppcp_params.slave_latency = BLE_SLAVE_LATENCY; + ppcp_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT; uint32_t err_code = sd_ble_gap_ppcp_set(&ppcp_params); if (err_code == NRF_SUCCESS) { - BLE_DEBUG_PRINTLN("PPCP set: 15-30ms interval, 2s timeout"); + BLE_DEBUG_PRINTLN("PPCP set: %u-%ums interval, latency=%u, %ums timeout", + ppcp_params.min_conn_interval * 5 / 4, // convert to ms (1.25ms units) + ppcp_params.max_conn_interval * 5 / 4, + ppcp_params.slave_latency, + ppcp_params.conn_sup_timeout * 10); // convert to ms (10ms units) } else { BLE_DEBUG_PRINTLN("Failed to set PPCP: %lu", err_code); } @@ -153,8 +177,8 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { Bluefruit.ScanResponse.addName(); - Bluefruit.Advertising.setInterval(32, 244); - Bluefruit.Advertising.setFastTimeout(30); + Bluefruit.Advertising.setInterval(BLE_ADV_INTERVAL_MIN, BLE_ADV_INTERVAL_MAX); + Bluefruit.Advertising.setFastTimeout(BLE_ADV_FAST_TIMEOUT); Bluefruit.Advertising.restartOnDisconnect(true); @@ -163,6 +187,7 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { void SerialBLEInterface::clearBuffers() { send_queue_len = 0; recv_queue_len = 0; + _last_retry_attempt = 0; bleuart.flush(); } @@ -257,21 +282,30 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { BLE_DEBUG_PRINTLN("writeBytes: connection invalid, clearing send queue"); send_queue_len = 0; } else { - Frame frame_to_send = send_queue[0]; - - size_t written = bleuart.write(frame_to_send.buf, frame_to_send.len); - if (written == frame_to_send.len) { - BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]); - shiftSendQueueLeft(); - } else if (written > 0) { - BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len); - shiftSendQueueLeft(); - } else { - if (!isConnected()) { - BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame"); + unsigned long now = millis(); + bool throttle_active = (_last_retry_attempt > 0 && (now - _last_retry_attempt) < BLE_RETRY_THROTTLE_MS); + + if (!throttle_active) { + Frame frame_to_send = send_queue[0]; + + size_t written = bleuart.write(frame_to_send.buf, frame_to_send.len); + if (written == frame_to_send.len) { + BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]); + _last_retry_attempt = 0; + shiftSendQueueLeft(); + } else if (written > 0) { + BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len); + _last_retry_attempt = 0; shiftSendQueueLeft(); } else { - BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry"); + if (!isConnected()) { + BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame"); + _last_retry_attempt = 0; + shiftSendQueueLeft(); + } else { + BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry"); + _last_retry_attempt = now; + } } } } @@ -329,9 +363,9 @@ void SerialBLEInterface::onBleUartRX(uint16_t conn_handle) { if (avail > MAX_FRAME_SIZE) { BLE_DEBUG_PRINTLN("onBleUartRX: WARN: BLE RX overflow, avail=%d, draining all", avail); - uint8_t drain_buf[32]; + uint8_t drain_buf[BLE_RX_DRAIN_BUF_SIZE]; while (instance->bleuart.available() > 0) { - int chunk = instance->bleuart.available() > 32 ? 32 : instance->bleuart.available(); + int chunk = instance->bleuart.available() > BLE_RX_DRAIN_BUF_SIZE ? BLE_RX_DRAIN_BUF_SIZE : instance->bleuart.available(); instance->bleuart.readBytes(drain_buf, chunk); } continue; @@ -349,5 +383,5 @@ bool SerialBLEInterface::isConnected() const { } bool SerialBLEInterface::isWriteBusy() const { - return send_queue_len >= (FRAME_QUEUE_SIZE - 1); + return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3); } diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index 557b86c5e..25968d78f 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -13,6 +13,7 @@ class SerialBLEInterface : public BaseSerialInterface { bool _isDeviceConnected; uint16_t _conn_handle; unsigned long _last_health_check; + unsigned long _last_retry_attempt; struct Frame { uint8_t len; @@ -46,6 +47,7 @@ class SerialBLEInterface : public BaseSerialInterface { _isDeviceConnected = false; _conn_handle = BLE_CONN_HANDLE_INVALID; _last_health_check = 0; + _last_retry_attempt = 0; send_queue_len = 0; recv_queue_len = 0; } From a0cb0fe739c684d8de5b19e41efded0223197681 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:26:09 +0100 Subject: [PATCH 15/44] move showalert after saveprefs --- examples/companion_radio/ui-new/UITask.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 29cc59d33..8077627f8 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -892,14 +892,13 @@ void UITask::toggleGPS() { _sensors->setSettingValue("gps", "0"); _node_prefs->gps_enabled = 0; notify(UIEventType::ack); - showAlert("GPS: Disabled", 800); } else { _sensors->setSettingValue("gps", "1"); _node_prefs->gps_enabled = 1; notify(UIEventType::ack); - showAlert("GPS: Enabled", 800); } the_mesh.savePrefs(); + showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800); _next_refresh = 0; break; } @@ -913,13 +912,12 @@ void UITask::toggleBuzzer() { if (buzzer.isQuiet()) { buzzer.quiet(false); notify(UIEventType::ack); - showAlert("Buzzer: ON", 800); } else { buzzer.quiet(true); - showAlert("Buzzer: OFF", 800); } _node_prefs->buzzer_quiet = buzzer.isQuiet(); the_mesh.savePrefs(); + showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800); _next_refresh = 0; // trigger refresh #endif } From 5421f20b3be58908b1fb7a5d33ebeadaa80bd746 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 27 Dec 2025 20:46:28 +1100 Subject: [PATCH 16/44] * check for 'early receive' ACK --- src/Mesh.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 1bda9e962..0548c9073 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -78,6 +78,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { + // check for 'early received' ACK + if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { + int i = 0; + uint32_t ack_crc; + memcpy(&ack_crc, &pkt->payload[i], 4); i += 4; + if (i <= pkt->payload_len) { + onAckRecv(pkt, ack_crc); + } + } + if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { return forwardMultipartDirect(pkt); From 8f9cc0c476df65bb1bf7913f684c2c9bc2cc1a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sun, 28 Dec 2025 20:04:57 +0000 Subject: [PATCH 17/44] Add RS232 bridge environment configuration for ProMicro --- variants/promicro/platformio.ini | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 78ea5fa1e..15bb5ce67 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -53,6 +53,31 @@ build_flags = lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 +[env:ProMicro_repeater_bridge_rs232_serial1] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} + +<../examples/simple_repeater> + + + + + + +build_flags = + ${Promicro.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D DISPLAY_CLASS=SSD1306Display + -D WITH_RS232_BRIDGE=Serial1 + -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 +lib_deps = ${Promicro.lib_deps} + adafruit/RTClib @ ^2.1.3 + [env:ProMicro_room_server] extends = Promicro build_src_filter = ${Promicro.build_src_filter} From 39b773546d02497598958b4543093f2eb395a577 Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:51:36 +1100 Subject: [PATCH 18/44] Fixed T1000-E temperature and lux sensors --- variants/t1000-e/t1000e_sensors.cpp | 10 +++++++--- variants/t1000-e/target.cpp | 1 + variants/t1000-e/variant.cpp | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/variants/t1000-e/t1000e_sensors.cpp b/variants/t1000-e/t1000e_sensors.cpp index a5b443cf2..f02541382 100644 --- a/variants/t1000-e/t1000e_sensors.cpp +++ b/variants/t1000-e/t1000e_sensors.cpp @@ -5,7 +5,7 @@ #define HEATER_NTC_BX 4250 // thermistor coefficient B #define HEATER_NTC_RP 8250 // ohm, series resistance to thermistor #define HEATER_NTC_KA 273.15 // 25 Celsius at Kelvin -#define NTC_REF_VCC 3000 // mV, output voltage of LDO +#define NTC_REF_VCC 3300 // mV, max voltage of 3V3 sensor rail #define LIGHT_REF_VCC 2400 // static unsigned int ntc_res2[136] = { @@ -54,6 +54,7 @@ static int get_light_lv(unsigned int light_volt) { float Vout = 0, Vin = 0, Rt = 0, temp = 0; unsigned int light_level = 0; + // Seeed's firmware maps the photocell reading to a 0-100 % range rather than lux. if (light_volt <= 80) { light_level = 0; return light_level; @@ -75,7 +76,8 @@ float t1000e_get_temperature(void) { analogReference(AR_INTERNAL_3_0); analogReadResolution(12); delay(10); - vcc_v = (1000.0 * (analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE)) / 4096; + unsigned int rail_v = (1000.0 * (analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE)) / 4096; + vcc_v = (rail_v > NTC_REF_VCC) ? NTC_REF_VCC : rail_v; ntc_v = (1000.0 * AREF_VOLTAGE * analogRead(TEMP_SENSOR)) / 4096; digitalWrite(PIN_3V3_EN, LOW); digitalWrite(SENSOR_EN, LOW); @@ -87,6 +89,7 @@ uint32_t t1000e_get_light(void) { int lux = 0; unsigned int lux_v = 0; + digitalWrite(PIN_3V3_EN, HIGH); digitalWrite(SENSOR_EN, HIGH); analogReference(AR_INTERNAL_3_0); analogReadResolution(12); @@ -94,6 +97,7 @@ uint32_t t1000e_get_light(void) { lux_v = 1000 * analogRead(LUX_SENSOR) * AREF_VOLTAGE / 4096; lux = get_light_lv(lux_v); digitalWrite(SENSOR_EN, LOW); + digitalWrite(PIN_3V3_EN, LOW); return lux; -} \ No newline at end of file +} diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 2a6380d5d..82d958b5d 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -154,6 +154,7 @@ bool T1000SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } if (requester_permissions & TELEM_PERM_ENVIRONMENT) { + // Firmware reports light as a 0-100 % scale, but expose it via Luminosity so app labels it "Luminosity". telemetry.addLuminosity(TELEM_CHANNEL_SELF, t1000e_get_light()); telemetry.addTemperature(TELEM_CHANNEL_SELF, t1000e_get_temperature()); } diff --git a/variants/t1000-e/variant.cpp b/variants/t1000-e/variant.cpp index f17b3a8df..a598e3cad 100644 --- a/variants/t1000-e/variant.cpp +++ b/variants/t1000-e/variant.cpp @@ -67,6 +67,8 @@ void initVariant() // https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino/blob/fab7d30a997a1dfeef9d1d59bfb549adda73815a/cores/nRF5/wiring.c#L65-L69 pinMode(BATTERY_PIN, INPUT); + pinMode(TEMP_SENSOR, INPUT); + pinMode(LUX_SENSOR, INPUT); pinMode(EXT_CHRG_DETECT, INPUT); pinMode(EXT_PWR_DETECT, INPUT); pinMode(GPS_RESETB, INPUT); From b261c74f0a4e0f752b0b58a43985f9a222fa96d6 Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:51:51 +1100 Subject: [PATCH 19/44] EnvironmentSensorManager.cpp: Mitigate BME280 self-heating causing inaccurate readings. --- .../sensors/EnvironmentSensorManager.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2692ec9c8..77a791bde 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -192,6 +192,13 @@ bool EnvironmentSensorManager::begin() { if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID()); + // Reduce self-heating: single-shot conversions, light oversampling, long standby. + BME280.setSampling(Adafruit_BME280::MODE_FORCED, + Adafruit_BME280::SAMPLING_X1, // temperature + Adafruit_BME280::SAMPLING_X1, // pressure + Adafruit_BME280::SAMPLING_X1, // humidity + Adafruit_BME280::FILTER_OFF, + Adafruit_BME280::STANDBY_MS_1000); BME280_initialized = true; } else { BME280_initialized = false; @@ -359,10 +366,12 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen #if ENV_INCLUDE_BME280 if (BME280_initialized) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()/100); - telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); + if (BME280.takeForcedMeasurement()) { // trigger a fresh reading in forced mode + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()/100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); + } } #endif From 6ccd0036c4ff0013dfe020191732ac1179445df4 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 27 Dec 2025 15:23:23 +0700 Subject: [PATCH 20/44] To fix the default temperature to be overridden by external sensors (if any) --- examples/simple_repeater/MyMesh.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6ae6ac0a8..993b27796 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -174,17 +174,18 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - float temperature = board.getMCUTemperature(); - if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN - telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature - } - // query other sensors -- target specific if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { perm_mask = 0x00; // just base telemetry allowed } sensors.querySensors(perm_mask, telemetry); + // This default temperature will be overridden by external sensors (if any) + float temperature = board.getMCUTemperature(); + if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN + telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature + } + uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); return 4 + tlen; // reply_len From 36ce749c6baf36f1ec2eda77e1d586956d29fc5d Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 27 Dec 2025 15:25:21 +0700 Subject: [PATCH 21/44] To get and average the temperature so it is more accurate, especially in low temperature --- src/helpers/ESP32Board.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 64c92c43d..61f814fd9 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -44,7 +44,14 @@ class ESP32Board : public mesh::MainBoard { // Temperature from ESP32 MCU float getMCUTemperature() override { - return temperatureRead(); + uint32_t raw = 0; + + // To get and average the temperature so it is more accurate, especially in low temperature + for (int i = 0; i < 4; i++) { + raw += temperatureRead(); + } + + return raw / 4; } uint8_t getStartupReason() const override { return startup_reason; } From 4a2111b442e64faed19bebe87ebd0a9cfce9a61d Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Sat, 20 Dec 2025 23:06:17 +1100 Subject: [PATCH 22/44] Fix TX LED stuck on when StartTransmit() fails --- src/helpers/radiolib/RadioLibWrappers.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 9014743a3..e34078211 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -137,6 +137,7 @@ bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { } MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err); idle(); // trigger another startRecv() + _board->onAfterTransmit(); return false; } From 4bfff823db215444d27850935a863fed227a120b Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 23 Dec 2025 12:48:08 +0700 Subject: [PATCH 23/44] Added powersaving to all ESP32 boards with RTC-supported DIO1 Added CLI to enable/disable powersaving --- examples/simple_repeater/MyMesh.cpp | 5 +++++ examples/simple_repeater/MyMesh.h | 3 +++ examples/simple_repeater/main.cpp | 18 ++++++++++++++++++ src/MeshCore.h | 1 + src/helpers/CommonCLI.cpp | 18 ++++++++++++++++-- src/helpers/CommonCLI.h | 2 ++ src/helpers/ESP32Board.h | 23 +++++++++++++++++++++++ 7 files changed, 68 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 993b27796..ba06af34b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1116,3 +1116,8 @@ void MyMesh::loop() { uptime_millis += now - last_millis; last_millis = now; } + +// To get the current pending work +int MyMesh::hasPendingWork() const { + return _mgr->getOutboundCount(0xFFFFFFFF); +} diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index ed9f0c5fc..6dc4792e9 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -225,4 +225,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bridge.begin(); } #endif + + // To get the current pending work + int hasPendingWork() const; }; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7387e77e7..d67a7029c 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -19,12 +19,19 @@ void halt() { static char command[160]; +// For power saving +unsigned long lastActive = 0; // mark last active time +unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot + void setup() { Serial.begin(115200); delay(1000); board.begin(); + // For power saving + lastActive = millis(); // mark last active time since boot + #ifdef DISPLAY_CLASS if (display.begin()) { display.startFrame(); @@ -117,4 +124,15 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); + + if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled + the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep + if (the_mesh.hasPendingWork() == 0) { // No pending work. Safe to sleep + board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet + lastActive = millis(); + nextSleepinSecs = 5; // Default: To work for 5s and sleep again + } else { + nextSleepinSecs += 5; // When there is pending work, to work another 5s + } + } } diff --git a/src/MeshCore.h b/src/MeshCore.h index eb7940589..718660d3b 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -51,6 +51,7 @@ class MainBoard { virtual void onAfterTransmit() { } virtual void reboot() = 0; virtual void powerOff() { /* no op */ } + virtual void sleep(uint32_t secs) { /* no op */ } virtual uint32_t getGpio() { return 0; } virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index a3de990aa..af27a9021 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -65,7 +65,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 - file.read(pad, 4); // 152 + file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 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 @@ -145,7 +145,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 - file.write(pad, 4); // 152 + file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 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 @@ -676,6 +676,20 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "Can't find GPS"); } #endif + } else if (memcmp(command, "powersaving on", 14) == 0) { + _prefs->powersaving_enabled = 1; + savePrefs(); + strcpy(reply, "ok"); // TODO: to return Not supported if required + } else if (memcmp(command, "powersaving off", 15) == 0) { + _prefs->powersaving_enabled = 0; + savePrefs(); + strcpy(reply, "ok"); + } else if (memcmp(command, "powersaving", 11) == 0) { + if (_prefs->powersaving_enabled) { + strcpy(reply, "on"); + } else { + strcpy(reply, "off"); + } } else if (memcmp(command, "log start", 9) == 0) { _callbacks->setLoggingOn(true); strcpy(reply, " logging on"); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 068783ab1..ca1a56128 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -48,6 +48,8 @@ struct NodePrefs { // persisted to file uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; float adc_multiplier; + // Power setting + uint8_t powersaving_enabled; // boolean }; class CommonCLICallbacks { diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 61f814fd9..88deaa7cf 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include "driver/rtc_io.h" class ESP32Board : public mesh::MainBoard { protected: @@ -54,6 +56,27 @@ class ESP32Board : public mesh::MainBoard { return raw / 4; } + void enterLightSleep(uint32_t secs) { +#if defined(CONFIG_IDF_TARGET_ESP32S3) // Supported ESP32 variants + if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs + } + + esp_light_sleep_start(); // CPU enters light sleep + } +#endif + } + + void sleep(uint32_t secs) override { + if (WiFi.getMode() == WIFI_MODE_NULL) { // WiFi is off ~ No active OTA, safe to go to sleep + enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet + } + } + uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) From 8b57a627fe4345b6643f331f846e9301d4943544 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Dec 2025 11:00:34 +0700 Subject: [PATCH 24/44] Modified hasPendingWork to return bool --- examples/simple_repeater/MyMesh.cpp | 6 +++--- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_repeater/main.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index ba06af34b..33e32a68a 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1117,7 +1117,7 @@ void MyMesh::loop() { last_millis = now; } -// To get the current pending work -int MyMesh::hasPendingWork() const { - return _mgr->getOutboundCount(0xFFFFFFFF); +// To check if there is pending work +bool MyMesh::hasPendingWork() const { + return _mgr->getOutboundCount(0xFFFFFFFF) > 0; } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 6dc4792e9..343aa44f5 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -226,6 +226,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } #endif - // To get the current pending work - int hasPendingWork() const; + // To check if there is pending work + bool hasPendingWork() const; }; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d67a7029c..8c745613e 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -127,7 +127,7 @@ void loop() { if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep - if (the_mesh.hasPendingWork() == 0) { // No pending work. Safe to sleep + if (!the_mesh.hasPendingWork()) { // No pending work. Safe to sleep board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet lastActive = millis(); nextSleepinSecs = 5; // Default: To work for 5s and sleep again From a5a88d3717bf46c330732ab7eed586fa1c8e9f32 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Dec 2025 11:23:19 +0700 Subject: [PATCH 25/44] Added powersaving_enabled sanitization Moved powersaving_enabled to match serialization order --- src/helpers/CommonCLI.cpp | 2 ++ src/helpers/CommonCLI.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index af27a9021..43a49ac43 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -93,6 +93,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200); _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); + _prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1); + _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ca1a56128..642a4cce3 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -42,14 +42,14 @@ struct NodePrefs { // persisted to file uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200) uint8_t bridge_channel; // 1-14 (ESP-NOW only) char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only) + // Power setting + uint8_t powersaving_enabled; // boolean // Gps settings uint8_t gps_enabled; uint32_t gps_interval; // in seconds uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; float adc_multiplier; - // Power setting - uint8_t powersaving_enabled; // boolean }; class CommonCLICallbacks { From b9579138a7064003c85b4cc6efba6e3fc01ddc6d Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Dec 2025 11:47:19 +0700 Subject: [PATCH 26/44] Added extra check for P_LORA_DIO_1 before going to sleep --- src/helpers/ESP32Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 88deaa7cf..2a44dfed4 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -57,7 +57,7 @@ class ESP32Board : public mesh::MainBoard { } void enterLightSleep(uint32_t secs) { -#if defined(CONFIG_IDF_TARGET_ESP32S3) // Supported ESP32 variants +#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet From 5e4599ac534588a398c7565860ba0b58bb4e4759 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Dec 2025 12:04:39 +0700 Subject: [PATCH 27/44] Fixed T-Beam board to work with sleep --- src/helpers/esp32/TBeamBoard.h | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/helpers/esp32/TBeamBoard.h b/src/helpers/esp32/TBeamBoard.h index 74baebc3a..4ff955510 100644 --- a/src/helpers/esp32/TBeamBoard.h +++ b/src/helpers/esp32/TBeamBoard.h @@ -2,16 +2,7 @@ #if defined(TBEAM_SUPREME_SX1262) || defined(TBEAM_SX1262) || defined(TBEAM_SX1276) -#include -#include -#include "XPowersLib.h" -#include "helpers/ESP32Board.h" -#include -//#include -//#include -//#include -//#include - +// Define pin mappings BEFORE including ESP32Board.h so sleep() can use P_LORA_DIO_1 #ifdef TBEAM_SUPREME_SX1262 // LoRa radio module pins for TBeam S3 Supreme SX1262 #define P_LORA_DIO_0 -1 //NC @@ -90,6 +81,13 @@ // SX1276 // }; +// Include headers AFTER pin definitions so ESP32Board::sleep() can use P_LORA_DIO_1 +#include +#include +#include "XPowersLib.h" +#include "helpers/ESP32Board.h" +#include + class TBeamBoard : public ESP32Board { XPowersLibInterface *PMU = NULL; //PhysicalLayer * pl; From 4d576d21b51b50685d038b6702bbd677a78790f2 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Mon, 29 Dec 2025 21:49:13 +0700 Subject: [PATCH 28/44] Added pad after powersaving_enabled --- src/helpers/CommonCLI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 43a49ac43..78e1b5e0b 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -66,6 +66,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 + file.read(pad, 3); // 153 file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 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 @@ -148,6 +149,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 + file.write(pad, 3); // 153 file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 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 From 62fbeb2a7b76448d8ec8518fbe74b922c1f7db65 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Mon, 29 Dec 2025 22:38:05 +0700 Subject: [PATCH 29/44] Used esp_wifi_get_mode instead of WiFi.getMode() to reduce the code size --- src/helpers/ESP32Board.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 2a44dfed4..01b4c980c 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -8,7 +8,7 @@ #include #include #include -#include +#include "esp_wifi.h" #include "driver/rtc_io.h" class ESP32Board : public mesh::MainBoard { @@ -72,8 +72,12 @@ class ESP32Board : public mesh::MainBoard { } void sleep(uint32_t secs) override { - if (WiFi.getMode() == WIFI_MODE_NULL) { // WiFi is off ~ No active OTA, safe to go to sleep - enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet + // To check for WiFi status to see if there is active OTA + wifi_mode_t mode; + esp_err_t err = esp_wifi_get_mode(&mode); + + if (err != ESP_OK) { // WiFi is off ~ No active OTA, safe to go to sleep + enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet } } From e57cae357f2168586791622ff3df637d7c7810a5 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 31 Dec 2025 13:01:56 +0100 Subject: [PATCH 30/44] Add venv dirs to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index db044b5ad..50631d890 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ cmake-* .cache .ccls compile_commands.json +.venv/ +venv/ From 84f69183bfeeb606857e5f6709e6eddd576b3785 Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Tue, 30 Dec 2025 21:58:59 +1100 Subject: [PATCH 31/44] BUGFIX: replay protection on repeaters tripped by timestamp sent from companion node mobile app. Send the node's RTC timestamp for TXT_TYPE_CLI_DATA messages instead of the timestamp from the app (matches the sendRequest() code logic). --- examples/companion_radio/MyMesh.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9cbb2eba2..56894ddeb 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -903,6 +903,7 @@ void MyMesh::handleCmdFrame(size_t len) { int result; uint32_t expected_ack; if (txt_type == TXT_TYPE_CLI_DATA) { + msg_timestamp = getRTCClock()->getCurrentTimeUnique(); // Use node's RTC instead of app timestamp to avoid tripping replay protection result = sendCommandData(*recipient, msg_timestamp, attempt, text, est_timeout); expected_ack = 0; // no Ack expected } else { @@ -1880,4 +1881,4 @@ bool MyMesh::advert() { } else { return false; } -} \ No newline at end of file +} From 5442339785760f34836477743516a7c4adae6a16 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 2 Jan 2026 12:54:57 +1100 Subject: [PATCH 32/44] * added protocol_guide doc --- docs/protocol_guide.md | 1201 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1201 insertions(+) create mode 100644 docs/protocol_guide.md diff --git a/docs/protocol_guide.md b/docs/protocol_guide.md new file mode 100644 index 000000000..ceedbbf05 --- /dev/null +++ b/docs/protocol_guide.md @@ -0,0 +1,1201 @@ +# MeshCore Device Communication Protocol Guide + +This document provides a comprehensive guide for communicating with MeshCore devices over Bluetooth Low Energy (BLE). It is platform-agnostic and can be used for Android, iOS, Python, JavaScript, or any other platform that supports BLE. + +## ⚠️ Important Security Note + +**All secrets, hashes, and cryptographic values shown in this guide are EXAMPLE VALUES ONLY and are NOT real secrets.** + +- The secret `9b647d242d6e1c5883fde0c5cf5c4c5e` used in examples is a made-up example value +- All hex values, public keys, and hashes in examples are for demonstration purposes only +- **Never use example secrets in production** - always generate new cryptographically secure random secrets +- This guide is for protocol documentation only - implement proper security practices in your actual implementation + +## Table of Contents + +1. [BLE Connection](#ble-connection) +2. [Protocol Overview](#protocol-overview) +3. [Commands](#commands) +4. [Channel Management](#channel-management) +5. [Secret Generation and QR Codes](#secret-generation-and-qr-codes) +6. [Message Handling](#message-handling) +7. [Response Parsing](#response-parsing) +8. [Example Implementation Flow](#example-implementation-flow) + +--- + +## BLE Connection + +### Service and Characteristics + +MeshCore devices expose a BLE service with the following UUIDs: + +- **Service UUID**: `0000ff00-0000-1000-8000-00805f9b34fb` +- **RX Characteristic** (Device → Client): `0000ff01-0000-1000-8000-00805f9b34fb` +- **TX Characteristic** (Client → Device): `0000ff02-0000-1000-8000-00805f9b34fb` + +### Connection Steps + +1. **Scan for Devices** + - Scan for BLE devices advertising the MeshCore service UUID + - Filter by device name (typically contains "MeshCore" or similar) + - Note the device MAC address for reconnection + +2. **Connect to GATT** + - Connect to the device using the discovered MAC address + - Wait for connection to be established + +3. **Discover Services and Characteristics** + - Discover the service with UUID `0000ff00-0000-1000-8000-00805f9b34fb` + - Discover RX characteristic (`0000ff01-...`) for receiving data + - Discover TX characteristic (`0000ff02-...`) for sending commands + +4. **Enable Notifications** + - Subscribe to notifications on the RX characteristic + - Enable notifications/indications to receive data from the device + - On some platforms, you may need to write to a descriptor (e.g., `0x2902`) with value `0x01` or `0x02` + +5. **Send AppStart Command** + - Send the app start command (see [Commands](#commands)) to initialize communication + - Wait for OK response before sending other commands + +### Connection State Management + +- **Disconnected**: No connection established +- **Connecting**: Connection attempt in progress +- **Connected**: GATT connection established, ready for commands +- **Error**: Connection failed or lost + +**Note**: MeshCore devices may disconnect after periods of inactivity. Implement auto-reconnect logic with exponential backoff. + +### BLE Write Type + +When writing commands to the TX characteristic, specify the write type: + +- **Write with Response** (default): Waits for acknowledgment from device +- **Write without Response**: Faster but no acknowledgment + +**Platform-specific**: +- **Android**: Use `BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT` or `WRITE_TYPE_NO_RESPONSE` +- **iOS**: Use `CBCharacteristicWriteType.withResponse` or `.withoutResponse` +- **Python (bleak)**: Use `write_gatt_char()` with `response=True` or `False` + +**Recommendation**: Use write with response for reliability, especially for critical commands like `SET_CHANNEL`. + +### MTU (Maximum Transmission Unit) + +The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (66 bytes), you may need to: + +1. **Request Larger MTU**: Request MTU of 512 bytes if supported + - Android: `gatt.requestMtu(512)` + - iOS: `peripheral.maximumWriteValueLength(for:)` + - Python (bleak): MTU is negotiated automatically + +2. **Handle Chunking**: If MTU is small, commands may be split automatically by the BLE stack + - Ensure all chunks are sent before waiting for response + - Responses may also arrive in chunks - buffer until complete + +### Command Sequencing and Timing + +**Critical**: Commands must be sent in the correct sequence: + +1. **After Connection**: + - Wait for GATT connection established + - Wait for services/characteristics discovered + - Wait for notifications enabled (descriptor write complete) + - **Wait 200-1000ms** for device to be ready (some devices need initialization time) + - Send `APP_START` command + - **Wait for `PACKET_OK` response** before sending any other commands + +2. **Command-Response Matching**: + - Send one command at a time + - Wait for response before sending next command + - Use timeout (typically 5 seconds) + - Match response to command by: + - Command type (e.g., `GET_CHANNEL` → `PACKET_CHANNEL_INFO`) + - Sequence number (if implemented) + - First-in-first-out queue + +3. **Timing Considerations**: + - Minimum delay between commands: 50-100ms + - After `APP_START`: Wait 200-500ms before next command + - After `SET_CHANNEL`: Wait 500-1000ms for channel to be created + - After enabling notifications: Wait 200ms before sending commands + +**Example Flow**: +```python +# 1. Connect and discover +await connect_to_device(device) +await discover_services() +await enable_notifications() +await asyncio.sleep(0.2) # Wait for device ready + +# 2. Send AppStart +send_command(build_app_start()) +response = await wait_for_response(PACKET_OK, timeout=5.0) +if response.type != PACKET_OK: + raise Exception("AppStart failed") + +# 3. Now safe to send other commands +await asyncio.sleep(0.1) # Small delay between commands +send_command(build_device_query()) +response = await wait_for_response(PACKET_DEVICE_INFO, timeout=5.0) +``` + +### Command Queue Management + +For reliable operation, implement a command queue: + +1. **Queue Structure**: + - Maintain a queue of pending commands + - Track which command is currently waiting for response + - Only send next command after receiving response or timeout + +2. **Implementation**: +```python +class CommandQueue: + def __init__(self): + self.queue = [] + self.waiting_for_response = False + self.current_command = None + + async def send_command(self, command, expected_response_type, timeout=5.0): + if self.waiting_for_response: + # Queue the command + self.queue.append((command, expected_response_type, timeout)) + return + + self.waiting_for_response = True + self.current_command = (command, expected_response_type, timeout) + + # Send command + await write_to_tx_characteristic(command) + + # Wait for response + response = await wait_for_response(expected_response_type, timeout) + + self.waiting_for_response = False + self.current_command = None + + # Process next queued command + if self.queue: + next_cmd, next_type, next_timeout = self.queue.pop(0) + await self.send_command(next_cmd, next_type, next_timeout) + + return response +``` + +3. **Error Handling**: + - On timeout: Clear current command, process next in queue + - On error: Log error, clear current command, process next + - Don't block queue on single command failure + +--- + +## Protocol Overview + +The MeshCore protocol uses a binary format with the following structure: + +- **Commands**: Sent from client to device via TX characteristic +- **Responses**: Received from device via RX characteristic (notifications) +- **All multi-byte integers**: Little-endian byte order +- **All strings**: UTF-8 encoding + +### Packet Structure + +Most packets follow this format: +``` +[Packet Type (1 byte)] [Data (variable length)] +``` + +The first byte indicates the packet type (see [Response Parsing](#response-parsing)). + +--- + +## Commands + +### 1. App Start + +**Purpose**: Initialize communication with the device. Must be sent first after connection. + +**Command Format**: +``` +Byte 0: 0x01 +Byte 1: 0x03 +Bytes 2-10: "mccli" (ASCII, null-padded to 9 bytes) +``` + +**Example** (hex): +``` +01 03 6d 63 63 6c 69 00 00 00 00 +``` + +**Response**: `PACKET_OK` (0x00) + +--- + +### 2. Device Query + +**Purpose**: Query device information. + +**Command Format**: +``` +Byte 0: 0x16 +Byte 1: 0x03 +``` + +**Example** (hex): +``` +16 03 +``` + +**Response**: `PACKET_DEVICE_INFO` (0x0D) with device information + +--- + +### 3. Get Channel Info + +**Purpose**: Retrieve information about a specific channel. + +**Command Format**: +``` +Byte 0: 0x1F +Byte 1: Channel Index (0-7) +``` + +**Example** (get channel 1): +``` +1F 01 +``` + +**Response**: `PACKET_CHANNEL_INFO` (0x12) with channel details + +**Note**: The device does not return channel secrets for security reasons. Store secrets locally when creating channels. + +--- + +### 4. Set Channel + +**Purpose**: Create or update a channel on the device. + +**Command Format**: +``` +Byte 0: 0x20 +Byte 1: Channel Index (0-7) +Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded) +Bytes 34-65: Secret (32 bytes, see [Secret Generation](#secret-generation)) +``` + +**Total Length**: 66 bytes + +**Channel Index**: +- Index 0: Reserved for public channels (no secret) +- Indices 1-7: Available for private channels + +**Channel Name**: +- UTF-8 encoded +- Maximum 32 bytes +- Padded with null bytes (0x00) if shorter + +**Secret Field** (32 bytes): +- For **private channels**: 32-byte secret (see [Secret Generation](#secret-generation)) +- For **public channels**: All zeros (0x00) + +**Example** (create channel "YourChannelName" at index 1 with secret): +``` +20 01 53 4D 53 00 00 ... (name padded to 32 bytes) + [32 bytes of secret] +``` + +**Response**: `PACKET_OK` (0x00) on success, `PACKET_ERROR` (0x01) on failure + +--- + +### 5. Send Channel Message + +**Purpose**: Send a text message to a channel. + +**Command Format**: +``` +Byte 0: 0x03 +Byte 1: 0x00 +Byte 2: Channel Index (0-7) +Bytes 3-6: Timestamp (32-bit little-endian Unix timestamp, seconds) +Bytes 7+: Message Text (UTF-8, variable length) +``` + +**Timestamp**: Unix timestamp in seconds (32-bit unsigned integer, little-endian) + +**Example** (send "Hello" to channel 1 at timestamp 1234567890): +``` +03 00 01 D2 02 96 49 48 65 6C 6C 6F +``` + +**Response**: `PACKET_MSG_SENT` (0x06) on success + +--- + +### 6. Get Message + +**Purpose**: Request the next queued message from the device. + +**Command Format**: +``` +Byte 0: 0x0A +``` + +**Example** (hex): +``` +0A +``` + +**Response**: +- `PACKET_CHANNEL_MSG_RECV` (0x08) or `PACKET_CHANNEL_MSG_RECV_V3` (0x11) for channel messages +- `PACKET_CONTACT_MSG_RECV` (0x07) or `PACKET_CONTACT_MSG_RECV_V3` (0x10) for contact messages +- `PACKET_NO_MORE_MSGS` (0x0A) if no messages available + +**Note**: Poll this command periodically to retrieve queued messages. The device may also send `PACKET_MESSAGES_WAITING` (0x83) as a notification when messages are available. + +--- + +### 7. Get Battery + +**Purpose**: Query device battery level. + +**Command Format**: +``` +Byte 0: 0x14 +``` + +**Example** (hex): +``` +14 +``` + +**Response**: `PACKET_BATTERY` (0x0C) with battery percentage + +--- + +## Channel Management + +### Channel Types + +1. **Public Channels** (Index 0) + - No secret required + - Anyone with the channel name can join + - Use for open communication + +2. **Private Channels** (Indices 1-7) + - Require a 16-byte secret + - Secret is expanded to 32 bytes using SHA-512 (see [Secret Generation](#secret-generation)) + - Only devices with the secret can access the channel + +### Channel Lifecycle + +1. **Create Channel**: + - Choose an available index (1-7 for private channels) + - Generate or provide a 16-byte secret + - Send `SET_CHANNEL` command with name and secret + - **Store the secret locally** (device does not return it) + +2. **Query Channel**: + - Send `GET_CHANNEL` command with channel index + - Parse `PACKET_CHANNEL_INFO` response + - Note: Secret will be null in response (security feature) + +3. **Delete Channel**: + - Send `SET_CHANNEL` command with empty name and all-zero secret + - Or overwrite with a new channel + +### Channel Index Management + +- **Index 0**: Reserved for public channels +- **Indices 1-7**: Available for private channels +- If a channel exists at index 0 but should be private, migrate it to index 1-7 + +--- + +## Secret Generation and QR Codes + +### Secret Generation + +For private channels, generate a cryptographically secure 16-byte secret: + +**Pseudocode**: +```python +import secrets + +# Generate 16 random bytes +secret_bytes = secrets.token_bytes(16) + +# Convert to hex string for storage/sharing +secret_hex = secret_bytes.hex() # 32 hex characters +``` + +**Important**: Use a cryptographically secure random number generator (CSPRNG). Do not use predictable values. + +### Secret Expansion + +When sending the secret to the device via `SET_CHANNEL`, the 16-byte secret must be expanded to 32 bytes: + +**Process**: +1. Take the 16-byte secret +2. Compute SHA-512 hash: `hash = SHA-512(secret)` +3. Use the first 32 bytes of the hash as the secret field in the command + +**Pseudocode**: +```python +import hashlib + +secret_16_bytes = ... # Your 16-byte secret +sha512_hash = hashlib.sha512(secret_16_bytes).digest() # 64 bytes +secret_32_bytes = sha512_hash[:32] # First 32 bytes +``` + +This matches MeshCore's ED25519 key expansion method. + +### QR Code Format + +QR codes for sharing channel secrets use the following format: + +**URL Scheme**: +``` +meshcore://channel/add?name=&secret=<32HexChars> +``` + +**Parameters**: +- `name`: Channel name (URL-encoded if needed) +- `secret`: 32-character hexadecimal representation of the 16-byte secret + +**Example** (using example secret - NOT a real secret): +``` +meshcore://channel/add?name=YourChannelName&secret=9b647d242d6e1c5883fde0c5cf5c4c5e +``` + +**Alternative Formats** (for backward compatibility): + +1. **JSON Format**: +```json +{ + "name": "YourChannelName", + "secret": "9b647d242d6e1c5883fde0c5cf5c4c5e" +} +``` +*Note: The secret value above is an example only - generate your own secure random secret.* + +2. **Plain Hex** (32 hex characters): +``` +9b647d242d6e1c5883fde0c5cf5c4c5e +``` +*Note: This is an example hex value - always generate your own cryptographically secure random secret.* + +### QR Code Generation + +**Steps**: +1. Generate or use existing 16-byte secret +2. Convert to 32-character hex string (lowercase) +3. URL-encode the channel name +4. Construct the `meshcore://` URL +5. Generate QR code from the URL string + +**Example** (Python with `qrcode` library): +```python +import qrcode +from urllib.parse import quote +import secrets + +channel_name = "YourChannelName" +# Generate a real cryptographically secure secret (NOT the example value) +secret_bytes = secrets.token_bytes(16) +secret_hex = secret_bytes.hex() # This will be a different value each time + +# Example value shown in documentation: "9b647d242d6e1c5883fde0c5cf5c4c5e" +# DO NOT use the example value - always generate your own! + +url = f"meshcore://channel/add?name={quote(channel_name)}&secret={secret_hex}" +qr = qrcode.QRCode(version=1, box_size=10, border=5) +qr.add_data(url) +qr.make(fit=True) +img = qr.make_image(fill_color="black", back_color="white") +img.save("channel_qr.png") +``` + +### QR Code Scanning + +When scanning a QR code: + +1. **Parse URL Format**: + - Extract `name` and `secret` query parameters + - Validate secret is 32 hex characters + +2. **Parse JSON Format**: + - Parse JSON object + - Extract `name` and `secret` fields + +3. **Parse Plain Hex**: + - Extract only hex characters (0-9, a-f, A-F) + - Validate length is 32 characters + - Convert to lowercase + +4. **Validate Secret**: + - Must be exactly 32 hex characters (16 bytes) + - Convert hex string to bytes + +5. **Create Channel**: + - Use extracted name and secret + - Send `SET_CHANNEL` command + +--- + +## Message Handling + +### Receiving Messages + +Messages are received via the RX characteristic (notifications). The device sends: + +1. **Channel Messages**: + - `PACKET_CHANNEL_MSG_RECV` (0x08) - Standard format + - `PACKET_CHANNEL_MSG_RECV_V3` (0x11) - Version 3 with SNR + +2. **Contact Messages**: + - `PACKET_CONTACT_MSG_RECV` (0x07) - Standard format + - `PACKET_CONTACT_MSG_RECV_V3` (0x10) - Version 3 with SNR + +3. **Notifications**: + - `PACKET_MESSAGES_WAITING` (0x83) - Indicates messages are queued + +### Contact Message Format + +**Standard Format** (`PACKET_CONTACT_MSG_RECV`, 0x07): +``` +Byte 0: 0x07 (packet type) +Bytes 1-6: Public Key Prefix (6 bytes, hex) +Byte 7: Path Length +Byte 8: Text Type +Bytes 9-12: Timestamp (32-bit little-endian) +Bytes 13-16: Signature (4 bytes, only if txt_type == 2) +Bytes 17+: Message Text (UTF-8) +``` + +**V3 Format** (`PACKET_CONTACT_MSG_RECV_V3`, 0x10): +``` +Byte 0: 0x10 (packet type) +Byte 1: SNR (signed byte, multiplied by 4) +Bytes 2-3: Reserved +Bytes 4-9: Public Key Prefix (6 bytes, hex) +Byte 10: Path Length +Byte 11: Text Type +Bytes 12-15: Timestamp (32-bit little-endian) +Bytes 16-19: Signature (4 bytes, only if txt_type == 2) +Bytes 20+: Message Text (UTF-8) +``` + +**Parsing Pseudocode**: +```python +def parse_contact_message(data): + packet_type = data[0] + offset = 1 + + # Check for V3 format + if packet_type == 0x10: # V3 + snr_byte = data[offset] + snr = ((snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0) + offset += 3 # Skip SNR + reserved + + pubkey_prefix = data[offset:offset+6].hex() + offset += 6 + + path_len = data[offset] + txt_type = data[offset + 1] + offset += 2 + + timestamp = int.from_bytes(data[offset:offset+4], 'little') + offset += 4 + + # If txt_type == 2, skip 4-byte signature + if txt_type == 2: + offset += 4 + + message = data[offset:].decode('utf-8') + + return { + 'pubkey_prefix': pubkey_prefix, + 'path_len': path_len, + 'txt_type': txt_type, + 'timestamp': timestamp, + 'message': message, + 'snr': snr if packet_type == 0x10 else None + } +``` + +### Channel Message Format + +**Standard Format** (`PACKET_CHANNEL_MSG_RECV`, 0x08): +``` +Byte 0: 0x08 (packet type) +Byte 1: Channel Index (0-7) +Byte 2: Path Length +Byte 3: Text Type +Bytes 4-7: Timestamp (32-bit little-endian) +Bytes 8+: Message Text (UTF-8) +``` + +**V3 Format** (`PACKET_CHANNEL_MSG_RECV_V3`, 0x11): +``` +Byte 0: 0x11 (packet type) +Byte 1: SNR (signed byte, multiplied by 4) +Bytes 2-3: Reserved +Byte 4: Channel Index (0-7) +Byte 5: Path Length +Byte 6: Text Type +Bytes 7-10: Timestamp (32-bit little-endian) +Bytes 11+: Message Text (UTF-8) +``` + +**Parsing Pseudocode**: +```python +def parse_channel_message(data): + packet_type = data[0] + offset = 1 + + # Check for V3 format + if packet_type == 0x11: # V3 + snr_byte = data[offset] + snr = ((snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0) + offset += 3 # Skip SNR + reserved + + channel_idx = data[offset] + path_len = data[offset + 1] + txt_type = data[offset + 2] + timestamp = int.from_bytes(data[offset+3:offset+7], 'little') + message = data[offset+7:].decode('utf-8') + + return { + 'channel_idx': channel_idx, + 'timestamp': timestamp, + 'message': message, + 'snr': snr if packet_type == 0x11 else None + } +``` + +### Sending Messages + +Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)). + +**Important**: +- Messages are limited to 133 characters per MeshCore specification +- Long messages should be split into chunks +- Include a chunk indicator (e.g., "[1/3] message text") + +--- + +## Response Parsing + +### Packet Types + +| Value | Name | Description | +|-------|------|-------------| +| 0x00 | PACKET_OK | Command succeeded | +| 0x01 | PACKET_ERROR | Command failed | +| 0x02 | PACKET_CONTACT_START | Start of contact list | +| 0x03 | PACKET_CONTACT | Contact information | +| 0x04 | PACKET_CONTACT_END | End of contact list | +| 0x05 | PACKET_SELF_INFO | Device self-information | +| 0x06 | PACKET_MSG_SENT | Message sent confirmation | +| 0x07 | PACKET_CONTACT_MSG_RECV | Contact message (standard) | +| 0x08 | PACKET_CHANNEL_MSG_RECV | Channel message (standard) | +| 0x09 | PACKET_CURRENT_TIME | Current time response | +| 0x0A | PACKET_NO_MORE_MSGS | No more messages available | +| 0x0C | PACKET_BATTERY | Battery level | +| 0x0D | PACKET_DEVICE_INFO | Device information | +| 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) | +| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) | +| 0x12 | PACKET_CHANNEL_INFO | Channel information | +| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet | +| 0x82 | PACKET_ACK | Acknowledgment | +| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification | +| 0x88 | PACKET_LOG_DATA | RF log data (can be ignored) | + +### Parsing Responses + +**PACKET_OK** (0x00): +``` +Byte 0: 0x00 +Bytes 1-4: Optional value (32-bit little-endian integer) +``` + +**PACKET_ERROR** (0x01): +``` +Byte 0: 0x01 +Byte 1: Error code (optional) +``` + +**PACKET_CHANNEL_INFO** (0x12): +``` +Byte 0: 0x12 +Byte 1: Channel Index +Bytes 2-33: Channel Name (32 bytes, null-terminated) +Bytes 34-65: Secret (32 bytes, but device typically only returns 20 bytes total) +``` + +**Note**: The device may not return the full 66-byte packet. Parse what is available. The secret field is typically not returned for security reasons. + +**PACKET_DEVICE_INFO** (0x0D): +``` +Byte 0: 0x0D +Byte 1: Firmware Version (uint8) +Bytes 2+: Variable length based on firmware version + +For firmware version >= 3: +Byte 2: Max Contacts Raw (uint8, actual = value * 2) +Byte 3: Max Channels (uint8) +Bytes 4-7: BLE PIN (32-bit little-endian) +Bytes 8-19: Firmware Build (12 bytes, UTF-8, null-padded) +Bytes 20-59: Model (40 bytes, UTF-8, null-padded) +Bytes 60-79: Version (20 bytes, UTF-8, null-padded) +``` + +**Parsing Pseudocode**: +```python +def parse_device_info(data): + if len(data) < 2: + return None + + fw_ver = data[1] + info = {'fw_ver': fw_ver} + + if fw_ver >= 3 and len(data) >= 80: + info['max_contacts'] = data[2] * 2 + info['max_channels'] = data[3] + info['ble_pin'] = int.from_bytes(data[4:8], 'little') + info['fw_build'] = data[8:20].decode('utf-8').rstrip('\x00').strip() + info['model'] = data[20:60].decode('utf-8').rstrip('\x00').strip() + info['ver'] = data[60:80].decode('utf-8').rstrip('\x00').strip() + + return info +``` + +**PACKET_BATTERY** (0x0C): +``` +Byte 0: 0x0C +Bytes 1-2: Battery Level (16-bit little-endian, percentage 0-100) + +Optional (if data size > 3): +Bytes 3-6: Used Storage (32-bit little-endian, KB) +Bytes 7-10: Total Storage (32-bit little-endian, KB) +``` + +**Parsing Pseudocode**: +```python +def parse_battery(data): + if len(data) < 3: + return None + + level = int.from_bytes(data[1:3], 'little') + info = {'level': level} + + if len(data) > 3: + used_kb = int.from_bytes(data[3:7], 'little') + total_kb = int.from_bytes(data[7:11], 'little') + info['used_kb'] = used_kb + info['total_kb'] = total_kb + + return info +``` + +**PACKET_SELF_INFO** (0x05): +``` +Byte 0: 0x05 +Byte 1: Advertisement Type +Byte 2: TX Power +Byte 3: Max TX Power +Bytes 4-35: Public Key (32 bytes, hex) +Bytes 36-39: Advertisement Latitude (32-bit little-endian, divided by 1e6) +Bytes 40-43: Advertisement Longitude (32-bit little-endian, divided by 1e6) +Byte 44: Multi ACKs +Byte 45: Advertisement Location Policy +Byte 46: Telemetry Mode (bitfield) +Byte 47: Manual Add Contacts (bool) +Bytes 48-51: Radio Frequency (32-bit little-endian, divided by 1000.0) +Bytes 52-55: Radio Bandwidth (32-bit little-endian, divided by 1000.0) +Byte 56: Radio Spreading Factor +Byte 57: Radio Coding Rate +Bytes 58+: Device Name (UTF-8, variable length, null-terminated) +``` + +**Parsing Pseudocode**: +```python +def parse_self_info(data): + if len(data) < 36: + return None + + offset = 1 + info = { + 'adv_type': data[offset], + 'tx_power': data[offset + 1], + 'max_tx_power': data[offset + 2], + 'public_key': data[offset + 3:offset + 35].hex() + } + offset += 35 + + lat = int.from_bytes(data[offset:offset+4], 'little') / 1e6 + lon = int.from_bytes(data[offset+4:offset+8], 'little') / 1e6 + info['adv_lat'] = lat + info['adv_lon'] = lon + offset += 8 + + info['multi_acks'] = data[offset] + info['adv_loc_policy'] = data[offset + 1] + telemetry_mode = data[offset + 2] + info['telemetry_mode_env'] = (telemetry_mode >> 4) & 0b11 + info['telemetry_mode_loc'] = (telemetry_mode >> 2) & 0b11 + info['telemetry_mode_base'] = telemetry_mode & 0b11 + info['manual_add_contacts'] = data[offset + 3] > 0 + offset += 4 + + freq = int.from_bytes(data[offset:offset+4], 'little') / 1000.0 + bw = int.from_bytes(data[offset+4:offset+8], 'little') / 1000.0 + info['radio_freq'] = freq + info['radio_bw'] = bw + info['radio_sf'] = data[offset + 8] + info['radio_cr'] = data[offset + 9] + offset += 10 + + if offset < len(data): + name_bytes = data[offset:] + info['name'] = name_bytes.decode('utf-8').rstrip('\x00').strip() + + return info +``` + +**PACKET_MSG_SENT** (0x06): +``` +Byte 0: 0x06 +Byte 1: Message Type +Bytes 2-5: Expected ACK (4 bytes, hex) +Bytes 6-9: Suggested Timeout (32-bit little-endian, seconds) +``` + +**PACKET_ACK** (0x82): +``` +Byte 0: 0x82 +Bytes 1-6: ACK Code (6 bytes, hex) +``` + +### Error Codes + +**PACKET_ERROR** (0x01) may include an error code in byte 1: + +| Error Code | Description | +|------------|-------------| +| 0x00 | Generic error (no specific code) | +| 0x01 | Invalid command | +| 0x02 | Invalid parameter | +| 0x03 | Channel not found | +| 0x04 | Channel already exists | +| 0x05 | Channel index out of range | +| 0x06 | Secret mismatch | +| 0x07 | Message too long | +| 0x08 | Device busy | +| 0x09 | Not enough storage | + +**Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response. + +### Partial Packet Handling + +BLE notifications may arrive in chunks, especially for larger packets. Implement buffering: + +**Implementation**: +```python +class PacketBuffer: + def __init__(self): + self.buffer = bytearray() + self.expected_length = None + + def add_data(self, data): + self.buffer.extend(data) + + # Check if we have a complete packet + if len(self.buffer) >= 1: + packet_type = self.buffer[0] + + # Determine expected length based on packet type + expected = self.get_expected_length(packet_type) + + if expected is not None and len(self.buffer) >= expected: + # Complete packet + packet = bytes(self.buffer[:expected]) + self.buffer = self.buffer[expected:] + return packet + elif expected is None: + # Variable length packet - try to parse what we have + # Some packets have minimum length requirements + if self.can_parse_partial(packet_type): + return self.try_parse_partial() + + return None # Incomplete packet + + def get_expected_length(self, packet_type): + # Fixed-length packets + fixed_lengths = { + 0x00: 5, # PACKET_OK (minimum) + 0x01: 2, # PACKET_ERROR (minimum) + 0x0A: 1, # PACKET_NO_MORE_MSGS + 0x14: 3, # PACKET_BATTERY (minimum) + } + return fixed_lengths.get(packet_type) + + def can_parse_partial(self, packet_type): + # Some packets can be parsed partially + return packet_type in [0x12, 0x08, 0x11, 0x07, 0x10, 0x05, 0x0D] + + def try_parse_partial(self): + # Try to parse with available data + # Return packet if successfully parsed, None otherwise + # This is packet-type specific + pass +``` + +**Usage**: +```python +buffer = PacketBuffer() + +def on_notification_received(data): + packet = buffer.add_data(data) + if packet: + parse_and_handle_packet(packet) +``` + +### Response Handling + +1. **Command-Response Pattern**: + - Send command via TX characteristic + - Wait for response via RX characteristic (notification) + - Match response to command using sequence numbers or command type + - Handle timeout (typically 5 seconds) + - Use command queue to prevent concurrent commands + +2. **Asynchronous Messages**: + - Device may send messages at any time via RX characteristic + - Handle `PACKET_MESSAGES_WAITING` (0x83) by polling `GET_MESSAGE` command + - Parse incoming messages and route to appropriate handlers + - Buffer partial packets until complete + +3. **Response Matching**: + - Match responses to commands by expected packet type: + - `APP_START` → `PACKET_OK` + - `DEVICE_QUERY` → `PACKET_DEVICE_INFO` + - `GET_CHANNEL` → `PACKET_CHANNEL_INFO` + - `SET_CHANNEL` → `PACKET_OK` or `PACKET_ERROR` + - `SEND_CHANNEL_MESSAGE` → `PACKET_MSG_SENT` + - `GET_MESSAGE` → `PACKET_CHANNEL_MSG_RECV`, `PACKET_CONTACT_MSG_RECV`, or `PACKET_NO_MORE_MSGS` + - `GET_BATTERY` → `PACKET_BATTERY` + +4. **Timeout Handling**: + - Default timeout: 5 seconds per command + - On timeout: Log error, clear current command, proceed to next in queue + - Some commands may take longer (e.g., `SET_CHANNEL` may need 1-2 seconds) + - Consider longer timeout for channel operations + +5. **Error Recovery**: + - On `PACKET_ERROR`: Log error code, clear current command + - On connection loss: Clear command queue, attempt reconnection + - On invalid response: Log warning, clear current command, proceed + +--- + +## Example Implementation Flow + +### Initialization + +```python +# 1. Scan for MeshCore device +device = scan_for_device("MeshCore") + +# 2. Connect to BLE GATT +gatt = connect_to_device(device) + +# 3. Discover services and characteristics +service = discover_service(gatt, "0000ff00-0000-1000-8000-00805f9b34fb") +rx_char = discover_characteristic(service, "0000ff01-0000-1000-8000-00805f9b34fb") +tx_char = discover_characteristic(service, "0000ff02-0000-1000-8000-00805f9b34fb") + +# 4. Enable notifications on RX characteristic +enable_notifications(rx_char, on_notification_received) + +# 5. Send AppStart command +send_command(tx_char, build_app_start()) +wait_for_response(PACKET_OK) +``` + +### Creating a Private Channel + +```python +# 1. Generate 16-byte secret +secret_16_bytes = generate_secret(16) # Use CSPRNG +secret_hex = secret_16_bytes.hex() + +# 2. Expand secret to 32 bytes using SHA-512 +import hashlib +sha512_hash = hashlib.sha512(secret_16_bytes).digest() +secret_32_bytes = sha512_hash[:32] + +# 3. Build SET_CHANNEL command +channel_name = "YourChannelName" +channel_index = 1 # Use 1-7 for private channels +command = build_set_channel(channel_index, channel_name, secret_32_bytes) + +# 4. Send command +send_command(tx_char, command) +response = wait_for_response(PACKET_OK) + +# 5. Store secret locally (device won't return it) +store_channel_secret(channel_index, secret_hex) +``` + +### Sending a Message + +```python +# 1. Build channel message command +channel_index = 1 +message = "Hello, MeshCore!" +timestamp = int(time.time()) +command = build_channel_message(channel_index, message, timestamp) + +# 2. Send command +send_command(tx_char, command) +response = wait_for_response(PACKET_MSG_SENT) +``` + +### Receiving Messages + +```python +def on_notification_received(data): + packet_type = data[0] + + if packet_type == PACKET_CHANNEL_MSG_RECV or packet_type == PACKET_CHANNEL_MSG_RECV_V3: + message = parse_channel_message(data) + handle_channel_message(message) + elif packet_type == PACKET_MESSAGES_WAITING: + # Poll for messages + send_command(tx_char, build_get_message()) +``` + +### QR Code Sharing + +```python +import secrets +from urllib.parse import quote + +# 1. Generate QR code data +channel_name = "YourChannelName" +# Generate a real secret (NOT the example value from documentation) +secret_bytes = secrets.token_bytes(16) +secret_hex = secret_bytes.hex() + +# Example value in documentation: "9b647d242d6e1c5883fde0c5cf5c4c5e" +# DO NOT use example values - always generate your own secure random secrets! + +url = f"meshcore://channel/add?name={quote(channel_name)}&secret={secret_hex}" + +# 2. Generate QR code image +qr = qrcode.QRCode(version=1, box_size=10, border=5) +qr.add_data(url) +qr.make(fit=True) +img = qr.make_image(fill_color="black", back_color="white") + +# 3. Display or save QR code +img.save("channel_qr.png") +``` + +--- + +## Best Practices + +1. **Connection Management**: + - Implement auto-reconnect with exponential backoff + - Handle disconnections gracefully + - Store last connected device address for quick reconnection + +2. **Secret Management**: + - Always use cryptographically secure random number generators + - Store secrets securely (encrypted storage) + - Never log or transmit secrets in plain text + - Device does not return secrets - you must store them locally + +3. **Message Handling**: + - Poll `GET_MESSAGE` periodically or when `PACKET_MESSAGES_WAITING` is received + - Handle message chunking for long messages (>133 characters) + - Implement message deduplication to avoid processing the same message twice + +4. **Error Handling**: + - Implement timeouts for all commands (typically 5 seconds) + - Handle `PACKET_ERROR` responses appropriately + - Log errors for debugging but don't expose sensitive information + +5. **Channel Management**: + - Avoid using channel index 0 for private channels + - Migrate channels from index 0 to 1-7 if needed + - Query channels after connection to discover existing channels + +--- + +## Platform-Specific Notes + +### Android +- Use `BluetoothGatt` API +- Request `BLUETOOTH_CONNECT` and `BLUETOOTH_SCAN` permissions (Android 12+) +- Enable notifications by writing to descriptor `0x2902` with value `0x01` or `0x02` + +### iOS +- Use `CoreBluetooth` framework +- Implement `CBPeripheralDelegate` for notifications +- Request Bluetooth permissions in Info.plist + +### Python +- Use `bleak` library for cross-platform BLE support +- Handle async/await for BLE operations +- Use `asyncio` for command-response patterns + +### JavaScript/Node.js +- Use `noble` or `@abandonware/noble` for BLE +- Handle callbacks or promises for async operations +- Use `Buffer` for binary data manipulation + +--- + +## Troubleshooting + +### Connection Issues +- **Device not found**: Ensure device is powered on and advertising +- **Connection timeout**: Check Bluetooth permissions and device proximity +- **GATT errors**: Ensure proper service/characteristic discovery + +### Command Issues +- **No response**: Verify notifications are enabled, check connection state +- **Error responses**: Verify command format, check channel index validity +- **Timeout**: Increase timeout value or check device responsiveness + +### Message Issues +- **Messages not received**: Poll `GET_MESSAGE` command periodically +- **Duplicate messages**: Implement message deduplication using timestamps/hashes +- **Message truncation**: Split long messages into chunks + +### Secret/Channel Issues +- **Secret not working**: Verify secret expansion (SHA-512) is correct +- **Channel not found**: Query channels after connection to discover existing channels +- **Channel index 0**: Migrate to index 1-7 for private channels + +--- + +## References + +- MeshCore Python implementation: `meshcore_py-main/src/meshcore/` +- BLE GATT Specification: Bluetooth SIG Core Specification +- ED25519 Key Expansion: RFC 8032 + +--- + +**Last Updated**: 2025-01-01 +**Protocol Version**: Based on MeshCore v1.36.0+ + From 4eb396ac50ecc8fc7e62999162a731b5dc8de652 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Wed, 31 Dec 2025 13:40:05 +0100 Subject: [PATCH 33/44] ESP factory reset clear NVS too --- examples/companion_radio/DataStore.cpp | 5 ++++- examples/companion_radio/MyMesh.cpp | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 7f5761f3c..4faac9754 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -65,6 +65,7 @@ void DataStore::begin() { #if defined(ESP32) #include + #include #elif defined(RP2040_PLATFORM) #include #elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) @@ -172,7 +173,9 @@ bool DataStore::formatFileSystem() { #elif defined(RP2040_PLATFORM) return LittleFS.format(); #elif defined(ESP32) - return ((fs::SPIFFSFS *)_fs)->format(); + bool fs_success = ((fs::SPIFFSFS *)_fs)->format(); + esp_err_t nvs_err = nvs_flash_erase(); // no need to reinit, will be done by reboot + return fs_success && (nvs_err == ESP_OK); #else #error "need to implement format()" #endif diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 56894ddeb..7689708c0 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1614,6 +1614,10 @@ void MyMesh::handleCmdFrame(size_t len) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type } } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { + if (_serial) { + MESH_DEBUG_PRINTLN("Factory reset: disabling serial interface to prevent reconnects (BLE/WiFi)"); + _serial->disable(); // Phone app disconnects before we can send OK frame so it's safe here + } bool success = _store->formatFileSystem(); if (success) { writeOKFrame(); From 11e1bfef0e5af4a2ee8850131567438bdadc845b Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 3 Jan 2026 18:54:48 -0700 Subject: [PATCH 34/44] Fix typos in README and source comments --- README.md | 4 ++-- src/helpers/radiolib/CustomLR1110.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 73fa960c1..d3bcbbef9 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Please submit PR's using 'dev' as the base branch! For minor changes just submit your PR and I'll try to review it, but for anything more 'impactful' please open an Issue first and start a discussion. Is better to sound out what it is you want to achieve first, and try to come to a consensus on what the best approach is, especially when it impacts the structure or architecture of this codebase. Here are some general principals you should try to adhere to: -* Keep it simple. Please, don't think like a high-level lang programmer. Think embedded, and keep code concise, without any unecessary layers. +* Keep it simple. Please, don't think like a high-level lang programmer. Think embedded, and keep code concise, without any unnecessary layers. * No dynamic memory allocation, except during setup/begin functions. * Use the same brace and indenting style that's in the core source modules. (A .clang-format is prob going to be added soon, but please do NOT retroactively re-format existing code. This just creates unnecessary diffs that make finding problems harder) @@ -106,7 +106,7 @@ There are a number of fairly major features in the pipeline, with no particular - [ ] Core + Apps: support for LZW message compression - [ ] Core: dynamic CR (Coding Rate) for weak vs strong hops - [ ] Core: new framework for hosting multiple virtual nodes on one physical device -- [ ] V2 protocol spec: discussion and concensus around V2 packet protocol, including path hashes, new encryption specs, etc +- [ ] V2 protocol spec: discussion and consensus around V2 packet protocol, including path hashes, new encryption specs, etc ## 📞 Get Support diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index 2e536de5e..e4332013a 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -10,7 +10,7 @@ class CustomLR1110 : public LR1110 { size_t getPacketLength(bool update) override { size_t len = LR1110::getPacketLength(update); if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { - // we've just recieved a corrupted packet + // we've just received a corrupted packet // this may have triggered a bug causing subsequent packets to be shifted // call standby() to return radio to known-good state // recvRaw will call startReceive() to restart rx From 19c4d0a5b3de313b41aa439f21002db07daa1537 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Sat, 3 Jan 2026 16:32:36 +0100 Subject: [PATCH 35/44] fix compilation errors for m6 companion roles --- variants/thinknode_m6/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini index 16394ced9..db22073c0 100644 --- a/variants/thinknode_m6/platformio.ini +++ b/variants/thinknode_m6/platformio.ini @@ -90,7 +90,6 @@ build_src_filter = ${ThinkNode_M6.build_src_filter} + + +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M6.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -113,7 +112,6 @@ build_src_filter = ${ThinkNode_M6.build_src_filter} + + +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M6.lib_deps} densaugeo/base64 @ ~1.4.0 From f3c6f7042b164e78aa4c0afd256f4cd9ed953049 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 3 Jan 2026 15:39:57 +1300 Subject: [PATCH 36/44] implement frame header parising for wifi interface --- src/helpers/esp32/SerialWifiInterface.cpp | 85 +++++++++++++++++++++-- src/helpers/esp32/SerialWifiInterface.h | 7 ++ 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp index 2df9980af..9470c8271 100644 --- a/src/helpers/esp32/SerialWifiInterface.cpp +++ b/src/helpers/esp32/SerialWifiInterface.cpp @@ -54,6 +54,9 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { // switch active connection to new client client = newClient; + + // forget received frame header + received_frame_header = NULL; } @@ -86,13 +89,83 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { send_queue[i] = send_queue[i + 1]; } } else { - int len = client.available(); - if (len > 0) { - uint8_t buf[MAX_FRAME_SIZE + 4]; - client.readBytes(buf, len); - memcpy(dest, buf+3, len-3); // remove header (don't even check ... problems are on the other dir) - return len-3; + + // check if we are waiting for a frame header + if(received_frame_header == NULL){ + + // make sure we have received enough bytes for a frame header + // 3 bytes frame header = (1 byte frame type) + (2 bytes frame length as unsigned 16-bit little endian) + int frame_header_length = 3; + if(client.available() >= frame_header_length){ + + // read frame header + uint8_t frame_header[3]; + client.readBytes(frame_header, frame_header_length); + + // parse frame header + uint8_t frame_type = frame_header[0]; + uint16_t frame_length; + memcpy(&frame_length, &frame_header[1], 2); + + // we have received a frame header + received_frame_header = new FrameHeader(); + received_frame_header->type = frame_type; + received_frame_header->length = frame_length; + + } + + } + + // check if we have received a frame header + if(received_frame_header){ + + // make sure we have received enough bytes for the required frame length + int available = client.available(); + int frame_type = received_frame_header->type; + int frame_length = received_frame_header->length; + if(frame_length > available){ + WIFI_DEBUG_PRINTLN("Waiting for %d more bytes", frame_length - available); + return 0; + } + + // skip frames that are larger than MAX_FRAME_SIZE + if(frame_length > MAX_FRAME_SIZE){ + WIFI_DEBUG_PRINTLN("Skipping frame: length=%d is larger than MAX_FRAME_SIZE=%d", frame_length, MAX_FRAME_SIZE); + while(frame_length > 0){ + uint8_t skip[1]; + int skipped = client.read(skip, 1); + frame_length -= skipped; + } + received_frame_header = NULL; + return 0; + } + + // skip frames that are not expected type + // '<' is 0x3c which indicates a frame sent from app to radio + if(frame_type != '<'){ + WIFI_DEBUG_PRINTLN("Skipping frame: type=0x%x is unexpected", frame_type); + while(frame_length > 0){ + uint8_t skip[1]; + int skipped = client.read(skip, 1); + frame_length -= skipped; + } + received_frame_header = NULL; + return 0; + } + + // read frame data + uint8_t frame_data[MAX_FRAME_SIZE]; + client.readBytes(frame_data, frame_length); + + // copy frame to provided buffer + memcpy(dest, frame_data, frame_length); + + // ready for next frame + received_frame_header = NULL; + return frame_length; + } + } } diff --git a/src/helpers/esp32/SerialWifiInterface.h b/src/helpers/esp32/SerialWifiInterface.h index 2b6c6edd5..c7139b400 100644 --- a/src/helpers/esp32/SerialWifiInterface.h +++ b/src/helpers/esp32/SerialWifiInterface.h @@ -12,11 +12,18 @@ class SerialWifiInterface : public BaseSerialInterface { WiFiServer server; WiFiClient client; + struct FrameHeader { + uint8_t type; + uint16_t length; + }; + struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; }; + FrameHeader* received_frame_header = NULL; + #define FRAME_QUEUE_SIZE 4 int recv_queue_len; Frame recv_queue[FRAME_QUEUE_SIZE]; From 50fcba83d2c5a9d1794630972bc80da881d73fb3 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 3 Jan 2026 16:36:19 +1300 Subject: [PATCH 37/44] remove use of dynamic allocation --- src/helpers/esp32/SerialWifiInterface.cpp | 30 ++++++++++++++--------- src/helpers/esp32/SerialWifiInterface.h | 7 +++++- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp index 9470c8271..de5cce784 100644 --- a/src/helpers/esp32/SerialWifiInterface.cpp +++ b/src/helpers/esp32/SerialWifiInterface.cpp @@ -43,6 +43,15 @@ bool SerialWifiInterface::isWriteBusy() const { return false; } +bool SerialWifiInterface::hasReceivedFrameHeader() { + return received_frame_header.type != 0 && received_frame_header.length != 0; +} + +void SerialWifiInterface::resetReceivedFrameHeader() { + received_frame_header.type = 0; + received_frame_header.length = 0; +} + size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { // check if new client connected auto newClient = server.available(); @@ -56,7 +65,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { client = newClient; // forget received frame header - received_frame_header = NULL; + resetReceivedFrameHeader(); } @@ -91,7 +100,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { } else { // check if we are waiting for a frame header - if(received_frame_header == NULL){ + if(!hasReceivedFrameHeader()){ // make sure we have received enough bytes for a frame header // 3 bytes frame header = (1 byte frame type) + (2 bytes frame length as unsigned 16-bit little endian) @@ -108,21 +117,20 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { memcpy(&frame_length, &frame_header[1], 2); // we have received a frame header - received_frame_header = new FrameHeader(); - received_frame_header->type = frame_type; - received_frame_header->length = frame_length; + received_frame_header.type = frame_type; + received_frame_header.length = frame_length; } } // check if we have received a frame header - if(received_frame_header){ + if(hasReceivedFrameHeader()){ // make sure we have received enough bytes for the required frame length int available = client.available(); - int frame_type = received_frame_header->type; - int frame_length = received_frame_header->length; + int frame_type = received_frame_header.type; + int frame_length = received_frame_header.length; if(frame_length > available){ WIFI_DEBUG_PRINTLN("Waiting for %d more bytes", frame_length - available); return 0; @@ -136,7 +144,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { int skipped = client.read(skip, 1); frame_length -= skipped; } - received_frame_header = NULL; + resetReceivedFrameHeader(); return 0; } @@ -149,7 +157,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { int skipped = client.read(skip, 1); frame_length -= skipped; } - received_frame_header = NULL; + resetReceivedFrameHeader(); return 0; } @@ -161,7 +169,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { memcpy(dest, frame_data, frame_length); // ready for next frame - received_frame_header = NULL; + resetReceivedFrameHeader(); return frame_length; } diff --git a/src/helpers/esp32/SerialWifiInterface.h b/src/helpers/esp32/SerialWifiInterface.h index c7139b400..19291497f 100644 --- a/src/helpers/esp32/SerialWifiInterface.h +++ b/src/helpers/esp32/SerialWifiInterface.h @@ -22,7 +22,7 @@ class SerialWifiInterface : public BaseSerialInterface { uint8_t buf[MAX_FRAME_SIZE]; }; - FrameHeader* received_frame_header = NULL; + FrameHeader received_frame_header; #define FRAME_QUEUE_SIZE 4 int recv_queue_len; @@ -40,6 +40,8 @@ class SerialWifiInterface : public BaseSerialInterface { _isEnabled = false; _last_write = 0; send_queue_len = recv_queue_len = 0; + received_frame_header.type = 0; + received_frame_header.length = 0; } void begin(int port); @@ -54,6 +56,9 @@ class SerialWifiInterface : public BaseSerialInterface { size_t writeFrame(const uint8_t src[], size_t len) override; size_t checkRecvFrame(uint8_t dest[]) override; + + bool hasReceivedFrameHeader(); + void resetReceivedFrameHeader(); }; #if WIFI_DEBUG_LOGGING && ARDUINO From 1c04d77445c24db3702eb929becc92bf65fcb092 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 4 Jan 2026 17:43:25 +1300 Subject: [PATCH 38/44] simplify reading frame header --- src/helpers/esp32/SerialWifiInterface.cpp | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp index de5cce784..462e3ecc3 100644 --- a/src/helpers/esp32/SerialWifiInterface.cpp +++ b/src/helpers/esp32/SerialWifiInterface.cpp @@ -108,17 +108,8 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { if(client.available() >= frame_header_length){ // read frame header - uint8_t frame_header[3]; - client.readBytes(frame_header, frame_header_length); - - // parse frame header - uint8_t frame_type = frame_header[0]; - uint16_t frame_length; - memcpy(&frame_length, &frame_header[1], 2); - - // we have received a frame header - received_frame_header.type = frame_type; - received_frame_header.length = frame_length; + client.readBytes(&received_frame_header.type, 1); + client.readBytes((uint8_t*)&received_frame_header.length, 2); } @@ -161,12 +152,8 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { return 0; } - // read frame data - uint8_t frame_data[MAX_FRAME_SIZE]; - client.readBytes(frame_data, frame_length); - - // copy frame to provided buffer - memcpy(dest, frame_data, frame_length); + // read frame data to provided buffer + client.readBytes(dest, frame_length); // ready for next frame resetReceivedFrameHeader(); From 66ae02c64a2e3631586b8baa34b2e5b42f46179f Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 6 Jan 2026 14:49:15 +0000 Subject: [PATCH 39/44] OFFLINE_QUEUE_SIZE for Heltec Wifi companions OFFLINE_QUEUE_SIZE missing from h3/h4 wifi companions, causing them only store 16 messages. --- variants/heltec_v3/platformio.ini | 2 ++ variants/heltec_v4/platformio.ini | 1 + 2 files changed, 3 insertions(+) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 36c6386f6..dcb2873c0 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -189,6 +189,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -341,6 +342,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c26a5bc69..ecfd7889b 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -176,6 +176,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v4.build_src_filter} From cd2a955a3174d4a932ede4467176a172753e12f3 Mon Sep 17 00:00:00 2001 From: alex-vg <22611767+alex-vg@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:12:39 +0100 Subject: [PATCH 40/44] variants: Xiao_S3_WIO: Add WiFi companion env --- variants/xiao_s3_wio/platformio.ini | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 8979edc25..22bb4090a 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -195,6 +195,29 @@ lib_deps = densaugeo/base64 @ ~1.4.0 adafruit/Adafruit SSD1306 @ ^2.5.13 +[env:Xiao_S3_WIO_companion_radio_wifi] +extends = Xiao_S3_WIO +build_src_filter = ${Xiao_S3_WIO.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> +build_flags = + ${Xiao_S3_WIO.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='"password"' + ; -D BLE_DEBUG_LOGGING=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_S3_WIO.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Xiao_S3_WIO_sensor] extends = Xiao_S3_WIO build_src_filter = ${Xiao_S3_WIO.build_src_filter} From 5d76297c4bf6ff8ccdd209fc3316c19e447f4763 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 16 Dec 2025 20:27:48 +0100 Subject: [PATCH 41/44] EnvironmentSensorManager.cpp: Fix RAK4631 serial GPS detection Serial1 is always true. If we want to check for the presence of a GPS receiver, we need to check if any data was received. Signed-off-by: Frieder Schrempf --- src/helpers/sensors/EnvironmentSensorManager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 77a791bde..f0bb5654b 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -653,8 +653,7 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ _location = &RAK12500_provider; return true; - } - else if(Serial1){ + } else if (Serial1.available()) { MESH_DEBUG_PRINTLN("Serial GPS init correctly and is turned on"); if(PIN_GPS_EN){ gpsResetPin = PIN_GPS_EN; From 92fed3e81a0daf66d10cc7a7609e549992ef2c55 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 16 Dec 2025 20:28:20 +0100 Subject: [PATCH 42/44] EnvironmentSensorManager.cpp: Cleanup after failed RAK4631 GPS detection If no GPS was detected, revert the hardware to the initial state, otherwise we may see conflicts or increased power consumption on some boards. Signed-off-by: Frieder Schrempf --- src/helpers/sensors/EnvironmentSensorManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index f0bb5654b..b7238def9 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -615,6 +615,7 @@ void EnvironmentSensorManager::rakGPSInit(){ MESH_DEBUG_PRINTLN("No GPS found"); gps_active = false; gps_detected = false; + Serial1.end(); return; } @@ -663,6 +664,8 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ gps_detected = true; return true; } + + pinMode(ioPin, INPUT); MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next"); return false; } From c431e22b56c437ad9ca5435d73ca360a7d152093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Weyhm=C3=BCller?= Date: Wed, 7 Jan 2026 03:40:00 +0100 Subject: [PATCH 43/44] Apply #1331 to other WiFi companions --- variants/heltec_tracker_v2/platformio.ini | 1 + variants/heltec_v2/platformio.ini | 1 + variants/lilygo_tlora_v2_1/platformio.ini | 1 + variants/nibble_screen_connect/platformio.ini | 1 + variants/station_g2/platformio.ini | 1 + 5 files changed, 5 insertions(+) diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 61ccd4f4b..36de671e2 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -185,6 +185,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_v2.build_src_filter} diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 539cc52e8..f8cc93608 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -183,6 +183,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v2.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 9ef9734e5..f27f57fd9 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -136,6 +136,7 @@ build_flags = -D WIFI_SSID='"ssid"' -D WIFI_PWD='"password"' -D WIFI_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini index e18bcde16..0d3d46523 100644 --- a/variants/nibble_screen_connect/platformio.ini +++ b/variants/nibble_screen_connect/platformio.ini @@ -147,6 +147,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index b3ba2a863..1428221d0 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -226,6 +226,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} From 160e62137c3f9a275e173eb63fb14abe3c98fa30 Mon Sep 17 00:00:00 2001 From: Ferdia McKeogh Date: Tue, 6 Jan 2026 20:11:19 +0100 Subject: [PATCH 44/44] Fix capitalization in T1000-E manufacturer string --- variants/t1000-e/T1000eBoard.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 32f54bb38..f26bdf02e 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -34,7 +34,7 @@ class T1000eBoard : public NRF52BoardDCDC { } const char* getManufacturerName() const override { - return "Seeed Tracker T1000-e"; + return "Seeed Tracker T1000-E"; } int buttonStateChanged() { @@ -91,4 +91,4 @@ class T1000eBoard : public NRF52BoardDCDC { } // bool startOTAUpdate(const char* id, char reply[]) override; -}; \ No newline at end of file +};