From 45ab0e8cf7656ceff4e30041cece1a656a8f00d5 Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 5 Oct 2025 13:58:25 +0200 Subject: [PATCH 001/409] sensecap_indicator: initial espnow support --- src/helpers/ui/LGFXDisplay.cpp | 125 +++++++++++++++++ src/helpers/ui/LGFXDisplay.h | 44 ++++++ .../SCIndicatorDisplay.h | 129 ++++++++++++++++++ .../sensecap_indicator-espnow/platformio.ini | 50 +++++++ variants/sensecap_indicator-espnow/target.cpp | 56 ++++++++ variants/sensecap_indicator-espnow/target.h | 29 ++++ 6 files changed, 433 insertions(+) create mode 100644 src/helpers/ui/LGFXDisplay.cpp create mode 100644 src/helpers/ui/LGFXDisplay.h create mode 100644 variants/sensecap_indicator-espnow/SCIndicatorDisplay.h create mode 100644 variants/sensecap_indicator-espnow/platformio.ini create mode 100644 variants/sensecap_indicator-espnow/target.cpp create mode 100644 variants/sensecap_indicator-espnow/target.h diff --git a/src/helpers/ui/LGFXDisplay.cpp b/src/helpers/ui/LGFXDisplay.cpp new file mode 100644 index 0000000000..a53cbc620d --- /dev/null +++ b/src/helpers/ui/LGFXDisplay.cpp @@ -0,0 +1,125 @@ +#include "LGFXDisplay.h" + +bool LGFXDisplay::begin() { + turnOn(); + display->init(); + display->setRotation(1); + display->setBrightness(64); + display->setColorDepth(8); + display->setTextColor(TFT_WHITE); + + buffer.setColorDepth(8); + buffer.setPsram(true); + buffer.createSprite(width(), height()); + + return true; +} + +void LGFXDisplay::turnOn() { +// display->wakeup(); + if (!_isOn) { + display->wakeup(); + } + _isOn = true; +} + +void LGFXDisplay::turnOff() { + if (_isOn) { + display->sleep(); + } + _isOn = false; +} + +void LGFXDisplay::clear() { +// display->clearDisplay(); + buffer.clearDisplay(); +} + +void LGFXDisplay::startFrame(Color bkg) { +// display->startWrite(); +// display->getScanLine(); + buffer.clearDisplay(); + buffer.setTextColor(TFT_WHITE); +} + +void LGFXDisplay::setTextSize(int sz) { + buffer.setTextSize(sz); +} + +void LGFXDisplay::setColor(Color c) { + // _color = (c != 0) ? ILI9342_WHITE : ILI9342_BLACK; + switch (c) { + case DARK: + _color = TFT_BLACK; + break; + case LIGHT: + _color = TFT_WHITE; + break; + case RED: + _color = TFT_RED; + break; + case GREEN: + _color = TFT_GREEN; + break; + case BLUE: + _color = TFT_BLUE; + break; + case YELLOW: + _color = TFT_YELLOW; + break; + case ORANGE: + _color = TFT_ORANGE; + break; + default: + _color = TFT_WHITE; + } + buffer.setTextColor(_color); +} + +void LGFXDisplay::setCursor(int x, int y) { + buffer.setCursor(x, y); +} + +void LGFXDisplay::print(const char* str) { + buffer.println(str); +// Serial.println(str); +} + +void LGFXDisplay::fillRect(int x, int y, int w, int h) { + buffer.fillRect(x, y, w, h, _color); +} + +void LGFXDisplay::drawRect(int x, int y, int w, int h) { + buffer.drawRect(x, y, w, h, _color); +} + +void LGFXDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { + buffer.drawBitmap(x, y, bits, w, h, _color); +} + +uint16_t LGFXDisplay::getTextWidth(const char* str) { + return buffer.textWidth(str); +} + +void LGFXDisplay::endFrame() { + display->startWrite(); + if (UI_ZOOM != 1) { + buffer.pushRotateZoom(display, display->width()/2, display->height()/2 , 0, UI_ZOOM, UI_ZOOM); + } else { + buffer.pushSprite(display, 0, 0); + } + display->endWrite(); +} + +bool LGFXDisplay::getTouch(int *x, int *y) { + lgfx::v1::touch_point_t point; + display->getTouch(&point); + if (UI_ZOOM != 1) { + *x = point.x / UI_ZOOM; + *y = point.y / UI_ZOOM; + } else { + *x = point.x; + *y = point.y; + } + return (*x >= 0) && (*y >= 0); +} \ No newline at end of file diff --git a/src/helpers/ui/LGFXDisplay.h b/src/helpers/ui/LGFXDisplay.h new file mode 100644 index 0000000000..81d0239f28 --- /dev/null +++ b/src/helpers/ui/LGFXDisplay.h @@ -0,0 +1,44 @@ + +/* + * Base class for LovyanGFX supported display (works on ESP32 mainly) + * You can extend this class to support your display, providing your own LGFX + */ + +#pragma once + +#include + +#define LGFX_USE_V1 +#include + +#ifndef UI_ZOOM + #define UI_ZOOM 1 +#endif + +class LGFXDisplay : public DisplayDriver { +protected: + LGFX_Device* display; + LGFX_Sprite buffer; + + bool _isOn; + int _color = TFT_WHITE; + +public: + LGFXDisplay(int w, int h):DisplayDriver(w/UI_ZOOM, h/UI_ZOOM) {_isOn = false;} + bool begin(); + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char* str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + uint16_t getTextWidth(const char* str) override; + void endFrame() override; + virtual bool getTouch(int *x, int *y); +}; diff --git a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h new file mode 100644 index 0000000000..6a7e31771d --- /dev/null +++ b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h @@ -0,0 +1,129 @@ +#pragma once + +#include + +#define LGFX_USE_V1 +#include + +#include +#include + +class LGFX : public lgfx::LGFX_Device +{ + lgfx::Panel_ST7701 _panel_instance; + lgfx::Bus_RGB _bus_instance; + lgfx::Light_PWM _light_instance; + lgfx::Touch_FT5x06 _touch_instance; + +public: + const uint16_t screenWidth = 480; + const uint16_t screenHeight = 480; + + bool hasButton(void) { return true; } + + LGFX(void) + { + { + auto cfg = _panel_instance.config(); + cfg.memory_width = 480; + cfg.memory_height = 480; + cfg.panel_width = screenWidth; + cfg.panel_height = screenHeight; + cfg.offset_x = 0; + cfg.offset_y = 0; + cfg.offset_rotation = 1; + _panel_instance.config(cfg); + } + + { + auto cfg = _panel_instance.config_detail(); + cfg.pin_cs = 4 | IO_EXPANDER; + cfg.pin_sclk = 41; + cfg.pin_mosi = 48; + cfg.use_psram = 1; + _panel_instance.config_detail(cfg); + } + + { + auto cfg = _bus_instance.config(); + cfg.panel = &_panel_instance; + + cfg.freq_write = 8000000; + cfg.pin_henable = 18; + + cfg.pin_pclk = 21; + cfg.pclk_active_neg = 0; + cfg.pclk_idle_high = 0; + cfg.de_idle_high = 1; + + cfg.pin_hsync = 16; + cfg.hsync_polarity = 0; + cfg.hsync_front_porch = 10; + cfg.hsync_pulse_width = 8; + cfg.hsync_back_porch = 50; + + cfg.pin_vsync = 17; + cfg.vsync_polarity = 0; + cfg.vsync_front_porch = 10; + cfg.vsync_pulse_width = 8; + cfg.vsync_back_porch = 20; + + cfg.pin_d0 = 15; + cfg.pin_d1 = 14; + cfg.pin_d2 = 13; + cfg.pin_d3 = 12; + cfg.pin_d4 = 11; + cfg.pin_d5 = 10; + cfg.pin_d6 = 9; + cfg.pin_d7 = 8; + cfg.pin_d8 = 7; + cfg.pin_d9 = 6; + cfg.pin_d10 = 5; + cfg.pin_d11 = 4; + cfg.pin_d12 = 3; + cfg.pin_d13 = 2; + cfg.pin_d14 = 1; + cfg.pin_d15 = 0; + + _bus_instance.config(cfg); + } + _panel_instance.setBus(&_bus_instance); + + { + auto cfg = _light_instance.config(); + cfg.pin_bl = 45; + _light_instance.config(cfg); + } + _panel_instance.light(&_light_instance); + + { + auto cfg = _touch_instance.config(); + cfg.pin_cs = GPIO_NUM_NC; + cfg.x_min = 0; + cfg.x_max = 479; + cfg.y_min = 0; + cfg.y_max = 479; + cfg.pin_int = GPIO_NUM_NC; + cfg.pin_rst = GPIO_NUM_NC; + cfg.bus_shared = true; + cfg.offset_rotation = 0; + + cfg.i2c_port = 0; + cfg.i2c_addr = 0x48; + cfg.pin_sda = 39; + cfg.pin_scl = 40; + cfg.freq = 400000; + _touch_instance.config(cfg); + _panel_instance.setTouch(&_touch_instance); + } + + setPanel(&_panel_instance); + } +}; + +class SCIndicatorDisplay : public LGFXDisplay { + LGFX disp; +public: + SCIndicatorDisplay() : LGFXDisplay(480, 480) + { display=&disp; } +}; diff --git a/variants/sensecap_indicator-espnow/platformio.ini b/variants/sensecap_indicator-espnow/platformio.ini new file mode 100644 index 0000000000..064f3ae880 --- /dev/null +++ b/variants/sensecap_indicator-espnow/platformio.ini @@ -0,0 +1,50 @@ +[SenseCapIndicator-ESPNow] +extends = esp32_base +board = esp32-s3-devkitc-1 +board_build.arduino.memory_type = qio_opi +board_build.flash_mode = qio +board_build.psram_type = opi +board_upload.flash_size = 8MB +board_upload.maximum_size = 8388608 +board_build.partitions = default.csv +build_flags = + ${esp32_base.build_flags} + -D PIN_BOARD_SDA=39 + -D PIN_BOARD_SCL=40 + -D DISPLAY_CLASS=SCIndicatorDisplay + -D DISPLAY_LINES=21 + -D LINE_LENGTH=53 + -D DISABLE_WIFI_OTA=1 + -D IO_EXPANDER=0x40 + -D IO_EXPANDER_IRQ=42 + -D UI_ZOOM=3.5 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 + -D PIN_USER_BTN=38 + -D HAS_TOUCH + -I variants/sensecap_indicator-espnow +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/sensecap_indicator-espnow/*.cpp> + + + + + + +lib_deps=${esp32_base.lib_deps} + adafruit/Adafruit BusIO @ ^1.17.2 + lovyan03/LovyanGFX @ ^1.2.7 + +[env:SenseCapIndicator-ESPNow_comp_radio_usb] +extends =SenseCapIndicator-ESPNow +build_flags = + ${SenseCapIndicator-ESPNow.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 +build_src_filter = ${SenseCapIndicator-ESPNow.build_src_filter} + +<../examples/companion_radio/ui-new/*.cpp> + +<../examples/companion_radio/*.cpp> +lib_deps = + ${SenseCapIndicator-ESPNow.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp new file mode 100644 index 0000000000..efdaac6109 --- /dev/null +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -0,0 +1,56 @@ +#include +#include "target.h" +#include + +ESP32Board board; + +ESPNOWRadio radio_driver; + +ESP32RTCClock rtc_clock; +#if defined(ENV_INCLUDE_GPS) +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, (mesh::RTCClock*)&rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + #ifdef PIN_USER_BTN + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); + #endif +#endif + +bool radio_init() { + rtc_clock.begin(); + + radio_driver.init(); + + return true; // success +} + +uint32_t radio_get_rng_seed() { + return millis() + radio_driver.intID(); // TODO: where to get some entropy? +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + // no-op +} + +void radio_set_tx_power(uint8_t dbm) { + radio_driver.setTxPower(dbm); +} + +// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG: +// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html +class ESP_RNG : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + esp_fill_random(dest, sz); + } +}; + +mesh::LocalIdentity radio_new_identity() { + ESP_RNG rng; + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/sensecap_indicator-espnow/target.h b/variants/sensecap_indicator-espnow/target.h new file mode 100644 index 0000000000..bb78e9233b --- /dev/null +++ b/variants/sensecap_indicator-espnow/target.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#ifdef ENV_INCLUDE_GPS + #include +#endif +#ifdef DISPLAY_CLASS + #include "SCIndicatorDisplay.h" + #include +#endif + +extern ESP32Board board; +extern ESPNOWRadio radio_driver; +extern ESP32RTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From 0502bc370dcdce6c106632511e32f3ca7713ae2f Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 5 Oct 2025 19:23:52 +0200 Subject: [PATCH 002/409] CommonCLI: gps management commands --- examples/simple_repeater/MyMesh.cpp | 38 +++++++++++++++++++ examples/simple_repeater/MyMesh.h | 6 +++ src/helpers/CommonCLI.cpp | 13 +++++++ src/helpers/CommonCLI.h | 4 ++ src/helpers/sensors/LocationProvider.h | 1 + .../sensors/MicroNMEALocationProvider.h | 10 +++++ 6 files changed, 72 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index df945d45e0..93175eb6a1 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -758,6 +758,44 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { #endif } +void MyMesh::gpsGetStatus(char * reply) { + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + bool status = l->isActive(); + bool sync = l->isValid(); + int sats = l->satellitesCount(); + if (status) { + sprintf(reply, "on, %s, %d sats", sync?"fix":"no fix", sats); + } else { + strcpy(reply, "off"); + } + } else { + strcpy(reply, "Can't find GPS"); + } +} + +void MyMesh::gpsStart() { + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + l->begin(); + l->reset(); + } +} + +void MyMesh::gpsStop() { + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + l->stop(); + } +} + +void MyMesh::gpsSyncTime() { + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + l->syncTime(); + } +} + void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 05a8d13b59..19bef70e8d 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -176,6 +176,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; + // Gps mgmt cli callbacks + void gpsGetStatus(char * reply) override; + void gpsStart() override; + void gpsStop() override; + void gpsSyncTime() override; + mesh::LocalIdentity& getSelfId() override { return self_id; } void saveIdentity(const mesh::LocalIdentity& new_id) override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 68acdf2b1a..e674cbc273 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -401,6 +401,19 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); } else if (memcmp(command, "board", 5) == 0) { sprintf(reply, "%s", _board->getManufacturerName()); +#if ENV_INCLUDE_GPS == 1 + } else if (memcmp(command, "gps on", 6) == 0) { + _callbacks->gpsStart(); + strcpy(reply, "ok"); + } else if (memcmp(command, "gps off", 7) == 0) { + _callbacks->gpsStop(); + strcpy(reply, "ok"); + } else if (memcmp(command, "gps sync", 8) == 0) { + _callbacks->gpsSyncTime(); + strcpy(reply, "Waiting fix ..."); + } else if (memcmp(command, "gps", 3) == 0) { + _callbacks->gpsGetStatus(reply); +#endif } 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 ff8ff50ef3..08e5f988ab 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -50,6 +50,10 @@ class CommonCLICallbacks { virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; + virtual void gpsGetStatus(char * reply) {} + virtual void gpsStart() {} + virtual void gpsStop() {} + virtual void gpsSyncTime() {} }; class CommonCLI { diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index f93dec488f..f1c934e51b 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -21,4 +21,5 @@ class LocationProvider { virtual void begin() = 0; virtual void stop() = 0; virtual void loop() = 0; + virtual bool isActive() = 0; }; diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index ec82f25eb1..16344108b0 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -78,6 +78,16 @@ public : } } + bool isActive() override { + // directly read the enable pin if present as gps can be + // activated/deactivated outside of here ... + if (_pin_en != -1) { + return digitalRead(_pin_en) == PIN_GPS_EN_ACTIVE; + } else { + return true; // no enable so must be active + } + } + void syncTime() override { nmea.clear(); LocationProvider::syncTime(); } long getLatitude() override { return nmea.getLatitude(); } long getLongitude() override { return nmea.getLongitude(); } From e4f2d63b0abd850fe602e0ab045c6147ff2f4037 Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 5 Oct 2025 20:31:25 +0200 Subject: [PATCH 003/409] cli_gps: use sensormanger to toggle gps on/off to keep state coherent --- examples/simple_repeater/MyMesh.cpp | 44 +++++++++++++------ examples/simple_repeater/MyMesh.h | 3 ++ src/helpers/sensors/LocationProvider.h | 2 +- .../sensors/MicroNMEALocationProvider.h | 2 +- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 93175eb6a1..e5a8b6b394 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -761,11 +761,15 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { void MyMesh::gpsGetStatus(char * reply) { LocationProvider * l = sensors.getLocationProvider(); if (l != NULL) { - bool status = l->isActive(); - bool sync = l->isValid(); + bool enabled = l->isEnabled(); // is EN pin on ? + bool active = gpsGetState(); // is enabled at SensorManager level ? + bool fix = l->isValid(); // has fix ? int sats = l->satellitesCount(); - if (status) { - sprintf(reply, "on, %s, %d sats", sync?"fix":"no fix", sats); + if (enabled) { + sprintf(reply, "on, %s, %s, %d sats", + active?"active":"deactivated", + fix?"fix":"no fix", + sats); } else { strcpy(reply, "off"); } @@ -774,19 +778,33 @@ void MyMesh::gpsGetStatus(char * reply) { } } -void MyMesh::gpsStart() { - LocationProvider * l = sensors.getLocationProvider(); - if (l != NULL) { - l->begin(); - l->reset(); +bool MyMesh::gpsGetState() { + int num = sensors.getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(sensors.getSettingName(i), "gps") == 0) { + return !strcmp(sensors.getSettingValue(i), "1"); + } + } + return false; +} + +void MyMesh::gpsSetState(bool value) { + // toggle GPS on/off + int num = sensors.getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(sensors.getSettingName(i), "gps") == 0) { + sensors.setSettingValue("gps", value?"1":"0"); + break; + } } } + +void MyMesh::gpsStart() { + gpsSetState(true); +} void MyMesh::gpsStop() { - LocationProvider * l = sensors.getLocationProvider(); - if (l != NULL) { - l->stop(); - } + gpsSetState(false); } void MyMesh::gpsSyncTime() { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 19bef70e8d..880d104383 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -142,6 +142,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + bool gpsGetState(); + void gpsSetState(bool value); + public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index f1c934e51b..81d08652ed 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -21,5 +21,5 @@ class LocationProvider { virtual void begin() = 0; virtual void stop() = 0; virtual void loop() = 0; - virtual bool isActive() = 0; + virtual bool isEnabled() = 0; }; diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 16344108b0..fb29fd791d 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -78,7 +78,7 @@ public : } } - bool isActive() override { + bool isEnabled() override { // directly read the enable pin if present as gps can be // activated/deactivated outside of here ... if (_pin_en != -1) { From 7be65c148eac7d2f5b95b68b47c9db994a5ecd5a Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Mon, 6 Oct 2025 10:25:10 +0200 Subject: [PATCH 004/409] cli_gps: remove callbacks and add generic sensor set/get. --- examples/simple_repeater/MyMesh.cpp | 56 --------------------- examples/simple_repeater/MyMesh.h | 9 ---- src/helpers/CommonCLI.cpp | 77 ++++++++++++++++++++++++++--- src/helpers/CommonCLI.h | 8 +-- 4 files changed, 74 insertions(+), 76 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index e5a8b6b394..df945d45e0 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -758,62 +758,6 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { #endif } -void MyMesh::gpsGetStatus(char * reply) { - LocationProvider * l = sensors.getLocationProvider(); - if (l != NULL) { - bool enabled = l->isEnabled(); // is EN pin on ? - bool active = gpsGetState(); // is enabled at SensorManager level ? - bool fix = l->isValid(); // has fix ? - int sats = l->satellitesCount(); - if (enabled) { - sprintf(reply, "on, %s, %s, %d sats", - active?"active":"deactivated", - fix?"fix":"no fix", - sats); - } else { - strcpy(reply, "off"); - } - } else { - strcpy(reply, "Can't find GPS"); - } -} - -bool MyMesh::gpsGetState() { - int num = sensors.getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(sensors.getSettingName(i), "gps") == 0) { - return !strcmp(sensors.getSettingValue(i), "1"); - } - } - return false; -} - -void MyMesh::gpsSetState(bool value) { - // toggle GPS on/off - int num = sensors.getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(sensors.getSettingName(i), "gps") == 0) { - sensors.setSettingValue("gps", value?"1":"0"); - break; - } - } -} - -void MyMesh::gpsStart() { - gpsSetState(true); -} - -void MyMesh::gpsStop() { - gpsSetState(false); -} - -void MyMesh::gpsSyncTime() { - LocationProvider * l = sensors.getLocationProvider(); - if (l != NULL) { - l->syncTime(); - } -} - void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 880d104383..05a8d13b59 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -142,9 +142,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; - bool gpsGetState(); - void gpsSetState(bool value); - public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); @@ -179,12 +176,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; - // Gps mgmt cli callbacks - void gpsGetStatus(char * reply) override; - void gpsStart() override; - void gpsStop() override; - void gpsSyncTime() override; - mesh::LocalIdentity& getSelfId() override { return self_id; } void saveIdentity(const mesh::LocalIdentity& new_id) override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e674cbc273..ee029ec3c6 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -128,6 +128,27 @@ void CommonCLI::savePrefs() { _callbacks->savePrefs(); } +const char* CommonCLI::sensorGetCustomVar(const char* key) { + int num = sensors.getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(sensors.getSettingName(i), key) == 0) { + return sensors.getSettingValue(i); + } + } + return NULL; +} + +bool CommonCLI::sensorSetCustomVar(const char* key, const char* value) { + int num = sensors.getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(sensors.getSettingName(i), key) == 0) { + sensors.setSettingValue(key, value); + return true; + } + } + return false; +} + void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return @@ -401,18 +422,60 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); } else if (memcmp(command, "board", 5) == 0) { sprintf(reply, "%s", _board->getManufacturerName()); + } else if (memcmp(command, "sensor get ", 11) == 0) { + const char* key = command + 11; + const char* val = sensorGetCustomVar(key); + if (val != NULL) { + strcpy(reply, val); + } else { + strcpy(reply, "can't find custom var"); + } + } else if (memcmp(command, "sensor set ", 11) == 0) { + const char* args = &command[11]; + const char* value = strchr(args,' ') + 1; + char key [value-args+1]; + strncpy(key, args, value-args-1); + if (sensorSetCustomVar(key, value)) { + strcpy(reply, "ok"); + } else { + strcpy(reply, "can't find custom var"); + } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { - _callbacks->gpsStart(); - strcpy(reply, "ok"); + if (sensorSetCustomVar("gps", "1")) { + strcpy(reply, "ok"); + } else { + strcpy(reply, "gps toggle not found"); + } } else if (memcmp(command, "gps off", 7) == 0) { - _callbacks->gpsStop(); - strcpy(reply, "ok"); + if (sensorSetCustomVar("gps", "0")) { + strcpy(reply, "ok"); + } else { + strcpy(reply, "gps toggle not found"); + } } else if (memcmp(command, "gps sync", 8) == 0) { - _callbacks->gpsSyncTime(); - strcpy(reply, "Waiting fix ..."); + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + l->syncTime(); + } } else if (memcmp(command, "gps", 3) == 0) { - _callbacks->gpsGetStatus(reply); + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + bool enabled = l->isEnabled(); // is EN pin on ? + bool fix = l->isValid(); // has fix ? + int sats = l->satellitesCount(); + bool active = !strcmp(sensorGetCustomVar("gps"), "1"); + if (enabled) { + sprintf(reply, "on, %s, %s, %d sats", + active?"active":"deactivated", + fix?"fix":"no fix", + sats); + } else { + strcpy(reply, "off"); + } + } else { + strcpy(reply, "Can't find GPS"); + } #endif } else if (memcmp(command, "log start", 9) == 0) { _callbacks->setLoggingOn(true); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 08e5f988ab..d3e3a19d7a 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -2,6 +2,7 @@ #include "Mesh.h" #include +#include struct NodePrefs { // persisted to file float airtime_factor; @@ -50,10 +51,6 @@ class CommonCLICallbacks { virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; - virtual void gpsGetStatus(char * reply) {} - virtual void gpsStart() {} - virtual void gpsStop() {} - virtual void gpsSyncTime() {} }; class CommonCLI { @@ -67,6 +64,9 @@ class CommonCLI { void savePrefs(); void loadPrefsInt(FILESYSTEM* _fs, const char* filename); + const char* sensorGetCustomVar(const char* key); + bool sensorSetCustomVar(const char* key, const char* value); + public: CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks) : _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { } From 341b69e3c949cf5c52d5f51f4668fe52ccfaf7a6 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Mon, 6 Oct 2025 14:08:16 +0200 Subject: [PATCH 005/409] sensor list command --- src/helpers/CommonCLI.cpp | 27 ++++++++++++++++++- .../sensors/EnvironmentSensorManager.cpp | 25 ++++++++++------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index ee029ec3c6..e20de60915 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -439,7 +439,32 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "ok"); } else { strcpy(reply, "can't find custom var"); - } + } + } else if (memcmp(command, "sensor list", 11) == 0) { + char* dp = reply; + int start = 0; + int end = sensors.getNumSettings(); + if (strlen(command) > 11) { + start = _atoi(command+12); + } + if (start >= end) { + strcpy(reply, "no custom var"); + } else { + sprintf(dp, "%d vars\n", end); + dp = strchr(dp, 0); + int i; + for (i = start; i < end && (dp-reply < 134); i++) { + sprintf(dp, "%s=%s\n", + sensors.getSettingName(i), + sensors.getSettingValue(i)); + dp = strchr(dp, 0); + } + if (i < end) { + sprintf(dp, "... next:%d", i); + } else { + *(dp-1) = 0; // remove last CR + } + } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { if (sensorSetCustomVar("gps", "1")) { diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 99605ff3a8..aa51c85a20 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -387,27 +387,34 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen int EnvironmentSensorManager::getNumSettings() const { + int settings = 0; #if ENV_INCLUDE_GPS - return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected - #else - return 0; + if (gps_detected) settings++; // only show GPS setting if GPS is detected #endif + return settings; } const char* EnvironmentSensorManager::getSettingName(int i) const { + int settings = 0; #if ENV_INCLUDE_GPS - return (gps_detected && i == 0) ? "gps" : NULL; - #else - return NULL; + if (gps_detected && i == settings++) { + return "gps"; + } #endif + // convenient way to add params (needed for some tests) +// if (i == settings++) return "param.2"; + return NULL; } const char* EnvironmentSensorManager::getSettingValue(int i) const { + int settings = 0; #if ENV_INCLUDE_GPS - if (gps_detected && i == 0) { - return gps_active ? "1" : "0"; - } + if (gps_detected && i == settings++) { + return gps_active ? "1" : "0"; + } #endif + // convenient way to add params ... +// if (i == settings++) return "2"; return NULL; } From 6ed8e9d5142715b61a22fed6dc8627fb3b3476cc Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Mon, 6 Oct 2025 15:12:03 +0200 Subject: [PATCH 006/409] gps_cli: gps state is now saved and restored upon reboot --- examples/simple_repeater/MyMesh.cpp | 15 ++++++++ examples/simple_repeater/MyMesh.h | 4 +++ src/helpers/CommonCLI.cpp | 53 +++++++++++++---------------- src/helpers/CommonCLI.h | 6 ++-- src/helpers/SensorManager.h | 21 ++++++++++++ 5 files changed, 66 insertions(+), 33 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b2b47ac37e..1c6c0d777a 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -631,7 +631,12 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.bridge_pkt_src = 0; // logTx _prefs.bridge_baud = 115200; // baud rate _prefs.bridge_channel = 1; // channel 1 + StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret)); + + // GPS defaults + _prefs.gps_enabled = 0; + _prefs.gps_interval = 0; } void MyMesh::begin(FILESYSTEM *fs) { @@ -653,8 +658,18 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + +#if ENV_INCLUDE_GPS == 1 + applyGpsPrefs(); +#endif } +#if ENV_INCLUDE_GPS == 1 +void MyMesh::applyGpsPrefs() { + sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); +} +#endif + void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params pending_freq = freq; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index d77b74d877..ca85d1a4db 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -159,6 +159,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { _cli.savePrefs(_fs); } +#if ENV_INCLUDE_GPS == 1 + void applyGpsPrefs(); +#endif + void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; bool formatFileSystem() override; void sendSelfAdvertisement(int delay_millis) override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e6f8a3a629..2efdd00a8c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -62,8 +62,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128 file.read((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130 file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 - file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 132 - file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 133 + 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->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 + file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 + // 161 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -84,6 +88,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->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); + file.close(); } } @@ -131,8 +137,12 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128 file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130 file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 - file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 132 - file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 133 + 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->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 + file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 + // 161 file.close(); } @@ -147,27 +157,6 @@ void CommonCLI::savePrefs() { _callbacks->savePrefs(); } -const char* CommonCLI::sensorGetCustomVar(const char* key) { - int num = sensors.getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(sensors.getSettingName(i), key) == 0) { - return sensors.getSettingValue(i); - } - } - return NULL; -} - -bool CommonCLI::sensorSetCustomVar(const char* key, const char* value) { - int num = sensors.getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(sensors.getSettingName(i), key) == 0) { - sensors.setSettingValue(key, value); - return true; - } - } - return false; -} - void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return @@ -527,7 +516,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s", _board->getManufacturerName()); } else if (memcmp(command, "sensor get ", 11) == 0) { const char* key = command + 11; - const char* val = sensorGetCustomVar(key); + const char* val = sensors.getSettingByKey(key); if (val != NULL) { strcpy(reply, val); } else { @@ -538,7 +527,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch const char* value = strchr(args,' ') + 1; char key [value-args+1]; strncpy(key, args, value-args-1); - if (sensorSetCustomVar(key, value)) { + if (sensors.setSettingByKey(key, value)) { strcpy(reply, "ok"); } else { strcpy(reply, "can't find custom var"); @@ -570,13 +559,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { - if (sensorSetCustomVar("gps", "1")) { + if (sensors.setSettingByKey("gps", "1")) { + _prefs->gps_enabled = 1; + savePrefs(); strcpy(reply, "ok"); } else { strcpy(reply, "gps toggle not found"); } } else if (memcmp(command, "gps off", 7) == 0) { - if (sensorSetCustomVar("gps", "0")) { + if (sensors.setSettingByKey("gps", "0")) { + _prefs->gps_enabled = 0; + savePrefs(); strcpy(reply, "ok"); } else { strcpy(reply, "gps toggle not found"); @@ -592,7 +585,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch bool enabled = l->isEnabled(); // is EN pin on ? bool fix = l->isValid(); // has fix ? int sats = l->satellitesCount(); - bool active = !strcmp(sensorGetCustomVar("gps"), "1"); + bool active = !strcmp(sensors.getSettingByKey("gps"), "1"); if (enabled) { sprintf(reply, "on, %s, %s, %d sats", active?"active":"deactivated", diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 14e249de11..07523643f3 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -38,6 +38,9 @@ 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) + // Gps settings + uint8_t gps_enabled; + uint32_t gps_interval; // in seconds }; class CommonCLICallbacks { @@ -83,9 +86,6 @@ class CommonCLI { void savePrefs(); void loadPrefsInt(FILESYSTEM* _fs, const char* filename); - const char* sensorGetCustomVar(const char* key); - bool sensorSetCustomVar(const char* key, const char* value); - public: CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks) : _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { } diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 1ace622024..38c1d806b7 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -23,4 +23,25 @@ class SensorManager { virtual const char* getSettingValue(int i) const { return NULL; } virtual bool setSettingValue(const char* name, const char* value) { return false; } virtual LocationProvider* getLocationProvider() { return NULL; } + + // Helper functions to manage setting by keys (useful in many places ...) + const char* getSettingByKey(const char* key) { + int num = getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(getSettingName(i), key) == 0) { + return getSettingValue(i); + } + } + return NULL; + } + + bool setSettingByKey(const char* key, const char* value) { + int num = getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(getSettingName(i), key) == 0) { + return setSettingValue(key, value); + } + } + return false; + } }; From 9e3c2fc9d9f75cdcda0106529d5cae51eecfab5c Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Mon, 6 Oct 2025 15:30:18 +0200 Subject: [PATCH 007/409] gps_cli: gps also restored on sensors and rooms --- examples/simple_repeater/MyMesh.cpp | 6 ------ examples/simple_repeater/MyMesh.h | 10 ++++++---- examples/simple_room_server/MyMesh.cpp | 4 ++++ examples/simple_room_server/MyMesh.h | 6 ++++++ examples/simple_sensor/SensorMesh.cpp | 4 ++++ examples/simple_sensor/SensorMesh.h | 5 +++++ 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 1c6c0d777a..f7153da35d 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -664,12 +664,6 @@ void MyMesh::begin(FILESYSTEM *fs) { #endif } -#if ENV_INCLUDE_GPS == 1 -void MyMesh::applyGpsPrefs() { - sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); -} -#endif - void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params pending_freq = freq; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index ca85d1a4db..c45c141dcd 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -135,6 +135,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { return _prefs.multi_acks; } +#if ENV_INCLUDE_GPS == 1 + void applyGpsPrefs() { + sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + } +#endif + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; @@ -159,10 +165,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { _cli.savePrefs(_fs); } -#if ENV_INCLUDE_GPS == 1 - void applyGpsPrefs(); -#endif - void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; bool formatFileSystem() override; void sendSelfAdvertisement(int delay_millis) override; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 89f2afb3d1..d9a3639766 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -632,6 +632,10 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + +#if ENV_INCLUDE_GPS == 1 + applyGpsPrefs(); +#endif } void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index b2df60c310..60ef1e735e 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -149,6 +149,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; +#if ENV_INCLUDE_GPS == 1 + void applyGpsPrefs() { + sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + } +#endif + public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index ba41ca4526..00da006aa6 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -697,6 +697,10 @@ void SensorMesh::begin(FILESYSTEM* fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + +#if ENV_INCLUDE_GPS == 1 + applyGpsPrefs(); +#endif } bool SensorMesh::formatFileSystem() { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index d26bcb145c..cdc3940c30 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -149,4 +149,9 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { void sendAlert(const ClientInfo* c, Trigger* t); + #if ENV_INCLUDE_GPS == 1 + void applyGpsPrefs() { + sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + } +#endif }; From b588e3f1e3b8a75b1519c4f640cbe40f5587e9df Mon Sep 17 00:00:00 2001 From: Bill Plein <260078+bplein@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:31:32 -0500 Subject: [PATCH 008/409] Ikoka Nano Variant Created as a fork of the ikoka stick variant. - Updated for Ikoka Nano legacy pinout - Removed display support - Removed user button support - Retains I2C sensor support Tested with the ebytes E22 30W module, companion-ble and repeater firmware versions, with an I2C INA3221 power sensor. --- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp | 94 ++++++ variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 60 ++++ variants/ikoka_nano_nrf/platformio.ini | 312 ++++++++++++++++++ variants/ikoka_nano_nrf/target.cpp | 44 +++ variants/ikoka_nano_nrf/target.h | 28 ++ variants/ikoka_nano_nrf/variant.cpp | 86 +++++ variants/ikoka_nano_nrf/variant.h | 149 +++++++++ 7 files changed, 773 insertions(+) create mode 100644 variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp create mode 100644 variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h create mode 100644 variants/ikoka_nano_nrf/platformio.ini create mode 100644 variants/ikoka_nano_nrf/target.cpp create mode 100644 variants/ikoka_nano_nrf/target.h create mode 100644 variants/ikoka_nano_nrf/variant.cpp create mode 100644 variants/ikoka_nano_nrf/variant.h diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp new file mode 100644 index 0000000000..ee7996921c --- /dev/null +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp @@ -0,0 +1,94 @@ +#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"); +} + +void IkokaNanoNRFBoard::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + pinMode(PIN_VBAT, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, HIGH); + +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +#endif + +// pinMode(SX126X_POWER_EN, OUTPUT); +// digitalWrite(SX126X_POWER_EN, HIGH); + 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 new file mode 100644 index 0000000000..8484085b06 --- /dev/null +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +#ifdef XIAO_NRF52 + +class IkokaNanoNRFBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + #if defined(LED_BLUE) + // turn off that annoying blue LED before transmitting + digitalWrite(LED_BLUE, HIGH); + #endif + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + #if defined(LED_BLUE) + // do it after transmitting too, just in case + digitalWrite(LED_BLUE, HIGH); + #endif + } +#endif + + uint16_t getBattMilliVolts() override { + // Please read befor going further ;) + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + + // We can't drive VBAT_ENABLE to HIGH as long + // as we don't know wether we are charging or not ... + // this is a 3mA loss (4/1500) + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char *getManufacturerName() const override { + return MANUFACTURER_STRING; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char *id, char reply[]) override; +}; + +#endif diff --git a/variants/ikoka_nano_nrf/platformio.ini b/variants/ikoka_nano_nrf/platformio.ini new file mode 100644 index 0000000000..abfbcf6707 --- /dev/null +++ b/variants/ikoka_nano_nrf/platformio.ini @@ -0,0 +1,312 @@ +[nrf52840_xiao] +extends = nrf52_base +platform_packages = + toolchain-gccarmnoneeabi@~1.100301.0 + framework-arduinoadafruitnrf52 +board = seeed-xiao-afruitnrf52-nrf52840 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52_base.build_flags} + -D NRF52_PLATFORM -D XIAO_NRF52 + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 +lib_ignore = + BluetoothOTA + lvgl + lib5b4 +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + adafruit/Adafruit INA3221 Library @ ^1.0.1 + adafruit/Adafruit INA219 @ ^1.2.3 + adafruit/Adafruit AHTX0 @ ^2.0.5 + adafruit/Adafruit BME280 Library @ ^2.3.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[ikoka_nano_nrf_baseboard] +extends = nrf52840_xiao +;board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52840_xiao.build_flags} + -D P_LORA_TX_LED=11 + -I variants/ikoka_nano_nrf + -I src/helpers/nrf52 + -D DISPLAY_CLASS=NullDisplayDriver + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=D1 +; -D P_LORA_BUSY=D3 + -D P_LORA_BUSY=D2 ; specific to ikoka nano variant. +; -D P_LORA_RESET=D2 + -D P_LORA_RESET=D3 ; specific to ikoka nano variant. +; -D P_LORA_NSS=D4 + -D P_LORA_NSS=D0 ; specific to ikoka nano variant. +; -D SX126X_RXEN=D5 + -D SX126X_RXEN=D7 + -D SX126X_TXEN=RADIOLIB_NC + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_WIRE_SCL=5 ; specific to ikoka nano variant. + -D PIN_WIRE_SDA=4 ; specific to ikoka nano variant. + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 +debug_tool = jlink +upload_protocol = nrfutil + + +;;; abstracted hardware variants + +[ikoka_nano_nrf_e22_22dbm] +extends = ikoka_nano_nrf_baseboard +; No PA in this model, full 22dBm +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Nano-E22-22dBm (Xiao_nrf52)"' + -D LORA_TX_POWER=22 +build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + + + +<../variants/ikoka_nano_nrf> + +[ikoka_nano_nrf_e22_30dbm] +extends = ikoka_nano_nrf_baseboard +; limit txpower to 20dBm on E22-900M30S. Anything higher will +; cause distortion in the PA output. 20dBm in -> 30dBm out +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Nano-E22-30dBm (Xiao_nrf52)"' + -D LORA_TX_POWER=20 +build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + + + +<../variants/ikoka_nano_nrf> + +[ikoka_nano_nrf_e22_33dbm] +extends = ikoka_nano_nrf_baseboard +; limit txpower to 9dBm on E22-900M33S to avoid hardware damage +; to the rf amplifier frontend. 9dBm in -> 33dBm out +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Nano-E22-33dBm (Xiao_nrf52)"' + -D LORA_TX_POWER=9 +build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + + + +<../variants/ikoka_nano_nrf> + +;;; abstracted firmware roles + +[ikoka_nano_nrf_companion_radio_ble] +extends = ikoka_nano_nrf_baseboard +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -I examples/companion_radio/ui-new +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ikoka_nano_nrf_baseboard.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[ikoka_nano_nrf_companion_radio_usb] +extends = ikoka_nano_nrf_baseboard +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -I examples/companion_radio/ui-new +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ikoka_nano_nrf_baseboard.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[ikoka_nano_nrf_repeater] +extends = ikoka_nano_nrf_baseboard +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D ADVERT_NAME='"Ikoka Nano 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 = ${ikoka_nano_nrf_baseboard.build_src_filter} + +<../examples/simple_repeater/*.cpp> + +[ikoka_nano_nrf_room_server] +extends = ikoka_nano_nrf_baseboard +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D ADVERT_NAME='"Ikoka Nano Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} + +<../examples/simple_room_server/*.cpp> + +;;; hardware + firmware variants + +;;; 22dBm EBYTE E22-900M22 variants + +[env:ikoka_nano_nrf_22dbm_companion_radio_usb] +extends = + ikoka_nano_nrf_e22_22dbm + ikoka_nano_nrf_companion_radio_usb +build_flags = + ${ikoka_nano_nrf_companion_radio_usb.build_flags} + ${ikoka_nano_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_usb.build_src_filter} + ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_nano_nrf_22dbm_companion_radio_ble] +extends = + ikoka_nano_nrf_e22_22dbm + ikoka_nano_nrf_companion_radio_ble +build_flags = + ${ikoka_nano_nrf_companion_radio_ble.build_flags} + ${ikoka_nano_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_ble.build_src_filter} + ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_nano_nrf_22dbm_repeater] +extends = + ikoka_nano_nrf_e22_22dbm + ikoka_nano_nrf_repeater +build_flags = + ${ikoka_nano_nrf_repeater.build_flags} + ${ikoka_nano_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_repeater.build_src_filter} + ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_nano_nrf_22dbm_room_server] +extends = + ikoka_nano_nrf_e22_22dbm + ikoka_nano_nrf_room_server +build_flags = + ${ikoka_nano_nrf_room_server.build_flags} + ${ikoka_nano_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_room_server.build_src_filter} + ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + + +;;; 30dBm EBYTE E22-900M30 variants + +[env:ikoka_nano_nrf_30dbm_companion_radio_usb] +extends = + ikoka_nano_nrf_e22_30dbm + ikoka_nano_nrf_companion_radio_usb +build_flags = + ${ikoka_nano_nrf_companion_radio_usb.build_flags} + ${ikoka_nano_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_usb.build_src_filter} + ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_nano_nrf_30dbm_companion_radio_ble] +extends = + ikoka_nano_nrf_e22_30dbm + ikoka_nano_nrf_companion_radio_ble +build_flags = + ${ikoka_nano_nrf_companion_radio_ble.build_flags} + ${ikoka_nano_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_ble.build_src_filter} + ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_nano_nrf_30dbm_repeater] +extends = + ikoka_nano_nrf_e22_30dbm + ikoka_nano_nrf_repeater +build_flags = + ${ikoka_nano_nrf_repeater.build_flags} + ${ikoka_nano_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_repeater.build_src_filter} + ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_nano_nrf_30dbm_room_server] +extends = + ikoka_nano_nrf_e22_30dbm + ikoka_nano_nrf_room_server +build_flags = + ${ikoka_nano_nrf_room_server.build_flags} + ${ikoka_nano_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_room_server.build_src_filter} + ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + + +;;; 33dBm EBYTE E22-900M33 variants + +[env:ikoka_nano_nrf_33dbm_companion_radio_usb] +extends = + ikoka_nano_nrf_e22_33dbm + ikoka_nano_nrf_companion_radio_usb +build_flags = + ${ikoka_nano_nrf_companion_radio_usb.build_flags} + ${ikoka_nano_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_usb.build_src_filter} + ${ikoka_nano_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_nano_nrf_33dbm_companion_radio_ble] +extends = + ikoka_nano_nrf_e22_33dbm + ikoka_nano_nrf_companion_radio_ble +build_flags = + ${ikoka_nano_nrf_companion_radio_ble.build_flags} + ${ikoka_nano_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_ble.build_src_filter} + ${ikoka_nano_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_nano_nrf_33dbm_repeater] +extends = + ikoka_nano_nrf_e22_33dbm + ikoka_nano_nrf_repeater +build_flags = + ${ikoka_nano_nrf_repeater.build_flags} + ${ikoka_nano_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_repeater.build_src_filter} + ${ikoka_nano_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_nano_nrf_33dbm_room_server] +extends = + ikoka_nano_nrf_e22_33dbm + ikoka_nano_nrf_room_server +build_flags = + ${ikoka_nano_nrf_room_server.build_flags} + ${ikoka_nano_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_room_server.build_src_filter} + ${ikoka_nano_nrf_e22_33dbm.build_src_filter} diff --git a/variants/ikoka_nano_nrf/target.cpp b/variants/ikoka_nano_nrf/target.cpp new file mode 100644 index 0000000000..aed591823b --- /dev/null +++ b/variants/ikoka_nano_nrf/target.cpp @@ -0,0 +1,44 @@ +#include +#include "target.h" +#include + +IkokaNanoNRFBoard board; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + // MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +EnvironmentSensorManager sensors; + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/ikoka_nano_nrf/target.h b/variants/ikoka_nano_nrf/target.h new file mode 100644 index 0000000000..9b4e908ecb --- /dev/null +++ b/variants/ikoka_nano_nrf/target.h @@ -0,0 +1,28 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + #include + extern DISPLAY_CLASS display; + // extern MomentaryButton user_btn; +#endif + +extern IkokaNanoNRFBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_nano_nrf/variant.cpp b/variants/ikoka_nano_nrf/variant.cpp new file mode 100644 index 0000000000..16542e273f --- /dev/null +++ b/variants/ikoka_nano_nrf/variant.cpp @@ -0,0 +1,86 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) + + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) + + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) + + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) + + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) + + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) + + // VBAT + 31, // D32 is P0.31 (VBAT) +}; + +void initVariant() +{ + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + //digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); + + // Low charging current (50mA) + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + //pinMode(PIN_CHARGING_CURRENT, INPUT); + + // High charging current (100mA) + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, LOW); + + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); +} diff --git a/variants/ikoka_nano_nrf/variant.h b/variants/ikoka_nano_nrf/variant.h new file mode 100644 index 0000000000..1496aad092 --- /dev/null +++ b/variants/ikoka_nano_nrf/variant.h @@ -0,0 +1,149 @@ +#ifndef _IKOKA_NANO_NRF_H_ +#define _IKOKA_NANO_NRF_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +//#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define PINS_COUNT (33) +#define NUM_DIGITAL_PINS (33) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (LED_RED) +#define LED_PWR (PINS_COUNT) +#define PIN_NEOPIXEL (PINS_COUNT) +#define NEOPIXEL_NUM (0) + +#define LED_BUILTIN (PIN_LED) + +#define LED_RED (11) +#define LED_GREEN (13) +#define LED_BLUE (12) + +#define LED_STATE_ON (0) // State when LED is litted + +// Buttons +// #define PIN_BUTTON1 (PINS_COUNT) + +// Digital PINs +static const uint8_t D0 = 0 ; +static const uint8_t D1 = 1 ; +static const uint8_t D2 = 2 ; +static const uint8_t D3 = 3 ; +static const uint8_t D4 = 4 ; +static const uint8_t D5 = 5 ; +static const uint8_t D6 = 6 ; +static const uint8_t D7 = 7 ; +static const uint8_t D8 = 8 ; +static const uint8_t D9 = 9 ; +static const uint8_t D10 = 10; + +#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define PIN_CHARGING_CURRENT (22) // Battery Charging current + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + +// Analog pins +#define PIN_A0 (0) +#define PIN_A1 (1) +#define PIN_A2 (2) +#define PIN_A3 (3) +#define PIN_A4 (4) +#define PIN_A5 (5) +#define PIN_VBAT (32) // Read the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define BAT_NOT_CHARGING (23) // LOW when charging + +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; + +#define ADC_RESOLUTION (12) + +// Other pins +#define PIN_NFC1 (30) +#define PIN_NFC2 (31) + +// Serial interfaces +#define PIN_SERIAL1_RX (7) +#define PIN_SERIAL1_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (9) +#define PIN_SPI_MOSI (10) +#define PIN_SPI_SCK (8) + +#define PIN_SPI1_MISO (25) +#define PIN_SPI1_MOSI (26) +#define PIN_SPI1_SCK (29) + +// Lora SPI is on SPI0 +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 ! +// #define PIN_WIRE_SCL (16) // use WIRE1_SDA + +static const uint8_t SDA = PIN_WIRE_SDA; +static const uint8_t SCL = PIN_WIRE_SCL; + +//#define PIN_WIRE1_SDA (17) +//#define PIN_WIRE1_SCL (16) +#define PIN_LSM6DS3TR_C_POWER (15) +#define PIN_LSM6DS3TR_C_INT1 (18) + +// PDM Interfaces +#define PIN_PDM_PWR (19) +#define PIN_PDM_CLK (20) +#define PIN_PDM_DIN (21) + +// QSPI Pins +#define PIN_QSPI_SCK (24) +#define PIN_QSPI_CS (25) +#define PIN_QSPI_IO0 (26) +#define PIN_QSPI_IO1 (27) +#define PIN_QSPI_IO2 (28) +#define PIN_QSPI_IO3 (29) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES (P25Q16H) +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From 8a2e4721d1dcb67663a0219e8bfd9cfd902b1fb0 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 10 Oct 2025 16:01:48 +0200 Subject: [PATCH 009/409] heltec wireless tracker: use `-D ARDUINO_USB_CDC_ON_BOOT=1` with all envs repeater and room server envs did not have arduino cdc flag enabled which resulted in broken serial. --- variants/heltec_tracker/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 33a7d9b895..19ab49a848 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -5,6 +5,7 @@ build_flags = ${esp32_base.build_flags} -I variants/heltec_tracker -D HELTEC_LORA_V3 + -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -40,7 +41,6 @@ build_flags = ${Heltec_tracker_base.build_flags} -I src/helpers/ui -I examples/companion_radio/ui-new - -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial -D DISPLAY_ROTATION=1 -D DISPLAY_CLASS=ST7735Display -D MAX_CONTACTS=300 From 70ac82059437007ee4c44ae246868af8940d2947 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Sat, 11 Oct 2025 18:01:26 +0800 Subject: [PATCH 010/409] add heltec tracker v2 board. --- boards/heltec_tracker_v2.json | 41 ++++ .../sensors/MicroNMEALocationProvider.h | 10 +- src/helpers/ui/ST7735Display.cpp | 9 +- .../HeltecTrackerV2Board.cpp | 88 +++++++ .../heltec_tracker_v2/HeltecTrackerV2Board.h | 23 ++ variants/heltec_tracker_v2/pins_arduino.h | 60 +++++ variants/heltec_tracker_v2/platformio.ini | 218 ++++++++++++++++++ variants/heltec_tracker_v2/target.cpp | 60 +++++ variants/heltec_tracker_v2/target.h | 30 +++ 9 files changed, 535 insertions(+), 4 deletions(-) create mode 100644 boards/heltec_tracker_v2.json create mode 100644 variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp create mode 100644 variants/heltec_tracker_v2/HeltecTrackerV2Board.h create mode 100644 variants/heltec_tracker_v2/pins_arduino.h create mode 100644 variants/heltec_tracker_v2/platformio.ini create mode 100644 variants/heltec_tracker_v2/target.cpp create mode 100644 variants/heltec_tracker_v2/target.h diff --git a/boards/heltec_tracker_v2.json b/boards/heltec_tracker_v2.json new file mode 100644 index 0000000000..277dcabd4a --- /dev/null +++ b/boards/heltec_tracker_v2.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "heltec_tracker_v2" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "heltec_tracker v2", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/", + "vendor": "heltec" +} \ No newline at end of file diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index ec82f25eb1..1cf9735833 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -3,6 +3,7 @@ #include "LocationProvider.h" #include #include +#include #ifndef GPS_EN #ifdef PIN_GPS_EN @@ -37,14 +38,15 @@ class MicroNMEALocationProvider : public LocationProvider { MicroNMEA nmea; mesh::RTCClock* _clock; Stream* _gps_serial; + RefCountedDigitalPin* _peripher_power; int _pin_reset; int _pin_en; long next_check = 0; long time_valid = 0; public : - MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN) : - _gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock) { + MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) : + _gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock), _peripher_power(peripher_power) { if (_pin_reset != -1) { pinMode(_pin_reset, OUTPUT); digitalWrite(_pin_reset, GPS_RESET_FORCE); @@ -56,6 +58,7 @@ public : } void begin() override { + if (_peripher_power) _peripher_power->claim(); if (_pin_en != -1) { digitalWrite(_pin_en, PIN_GPS_EN_ACTIVE); } @@ -75,7 +78,8 @@ public : void stop() override { if (_pin_en != -1) { digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE); - } + } + if (_peripher_power) _peripher_power->release(); } void syncTime() override { nmea.clear(); LocationProvider::syncTime(); } diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index e9eea69b7c..0a28077c06 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -24,14 +24,21 @@ bool ST7735Display::begin() { digitalWrite(PIN_TFT_LEDA_CTL, HIGH); digitalWrite(PIN_TFT_RST, HIGH); +#if defined(HELTEC_TRACKER_V2) + display.initR(INITR_MINI160x80); + display.setRotation(DISPLAY_ROTATION); + uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR + display.sendCommand(ST77XX_MADCTL, &madctl, 1); +#else display.initR(INITR_MINI160x80_PLUGIN); display.setRotation(DISPLAY_ROTATION); +#endif display.setSPISpeed(40000000); display.fillScreen(ST77XX_BLACK); display.setTextColor(ST77XX_WHITE); display.setTextSize(2); display.cp437(true); // Use full 256 char 'Code Page 437' font - + _isOn = true; } return true; diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp new file mode 100644 index 0000000000..4975d5cdeb --- /dev/null +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -0,0 +1,88 @@ +#include "HeltecTrackerV2Board.h" + +void HeltecTrackerV2Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + pinMode(P_LORA_PA_POWER, OUTPUT); + digitalWrite(P_LORA_PA_POWER,HIGH); + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN,HIGH); + pinMode(P_LORA_PA_TX_EN, OUTPUT); + digitalWrite(P_LORA_PA_TX_EN,LOW); + + periph_power.begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void HeltecTrackerV2Board::onBeforeTransmit(void) { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + digitalWrite(P_LORA_PA_TX_EN,HIGH); + } + + void HeltecTrackerV2Board::onAfterTransmit(void) { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + digitalWrite(P_LORA_PA_TX_EN,LOW); + } + + void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void HeltecTrackerV2Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t HeltecTrackerV2Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + delay(10); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecTrackerV2Board::getManufacturerName() const { + return "Heltec Tracker V2"; + } diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.h b/variants/heltec_tracker_v2/HeltecTrackerV2Board.h new file mode 100644 index 0000000000..d93c86cddb --- /dev/null +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include + +class HeltecTrackerV2Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecTrackerV2Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void onBeforeTransmit(void) override; + void onAfterTransmit(void) override; + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_tracker_v2/pins_arduino.h b/variants/heltec_tracker_v2/pins_arduino.h new file mode 100644 index 0000000000..982cb5e5a7 --- /dev/null +++ b/variants/heltec_tracker_v2/pins_arduino.h @@ -0,0 +1,60 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 18; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 3; +static const uint8_t LED = 18; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini new file mode 100644 index 0000000000..c4a79d9e47 --- /dev/null +++ b/variants/heltec_tracker_v2/platformio.ini @@ -0,0 +1,218 @@ +[Heltec_tracker_v2] +extends = esp32_base +board = heltec_tracker_v2 +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/heltec_tracker_v2 + -D HELTEC_TRACKER_V2 + -D ESP32_CPU_FREQ=160 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_TX_LED=18 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=12 + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 + -D P_LORA_PA_POWER=7 ;power en + -D P_LORA_PA_EN=4 + -D P_LORA_PA_TX_EN=46 ;enable tx + -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. + -D MAX_LORA_TX_POWER=22 ;Max SX1262 output + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=5 + -D PIN_BOARD_SCL=6 + -D PIN_USER_BTN=0 + -D PIN_TFT_SDA=42 ; SDIN + -D PIN_TFT_SCL=41 ; SCLK + -D PIN_TFT_DC=40 ; RS (register select) + -D PIN_TFT_RST=39 ; RES + -D PIN_TFT_CS=38 + -D USE_PIN_TFT=1 + -D PIN_VEXT_EN=3 ; Vext is connected to VDD which is also connected to OLED & GPS + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) + -D DISPLAY_ROTATION=1 + -D PIN_GPS_RX=34 + -D PIN_GPS_TX=33 + -D PIN_GPS_RESET=35 + -D PIN_GPS_RESET_ACTIVE=LOW + -D GPS_BAUD_RATE=115200 + -D ENV_INCLUDE_GPS=1 + -D PIN_ADC_CTRL=2 + -D PIN_VBAT_READ=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_tracker_v2> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:heltec_tracker_v2_repeater] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"Heltec Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:heltec_tracker_v2_repeater_bridge_espnow] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_tracker_v2_room_server] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_tracker_v2_terminal_chat] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_tracker_v2_companion_radio_usb] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=ST7735Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_tracker_v2_companion_radio_ble] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=ST7735Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + ; -D BLE_DEBUG_LOGGING=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_tracker_v2_companion_radio_wifi] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=ST7735Display + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_tracker_v2_sensor] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D ADVERT_NAME='"Heltec Tracker V2 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + -D DISPLAY_CLASS=ST7735Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + + + +<../examples/simple_sensor> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_tracker_v2/target.cpp b/variants/heltec_tracker_v2/target.cpp new file mode 100644 index 0000000000..da397fb741 --- /dev/null +++ b/variants/heltec_tracker_v2/target.cpp @@ -0,0 +1,60 @@ +#include +#include "target.h" + +HeltecTrackerV2Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, NULL, GPS_RESET, GPS_EN, &board.periph_power); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/heltec_tracker_v2/target.h b/variants/heltec_tracker_v2/target.h new file mode 100644 index 0000000000..190404ef99 --- /dev/null +++ b/variants/heltec_tracker_v2/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern HeltecTrackerV2Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From ad2894a0399d3337070ef5d0c8fa451c1322ddcd Mon Sep 17 00:00:00 2001 From: Quency-D Date: Sat, 11 Oct 2025 18:03:15 +0800 Subject: [PATCH 011/409] delete PSRAM. --- boards/heltec_tracker_v2.json | 1 - 1 file changed, 1 deletion(-) diff --git a/boards/heltec_tracker_v2.json b/boards/heltec_tracker_v2.json index 277dcabd4a..62b569e072 100644 --- a/boards/heltec_tracker_v2.json +++ b/boards/heltec_tracker_v2.json @@ -6,7 +6,6 @@ }, "core": "esp32", "extra_flags": [ - "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", "-DARDUINO_USB_MODE=0", "-DARDUINO_RUNNING_CORE=1", From 76dcfbb23a50cd026a78a9719f54bd0bba81dc39 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 11 Oct 2025 15:29:17 +0200 Subject: [PATCH 012/409] gpsCli: use parseTextParts --- src/helpers/CommonCLI.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 2efdd00a8c..6616a0562f 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -518,15 +518,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch const char* key = command + 11; const char* val = sensors.getSettingByKey(key); if (val != NULL) { - strcpy(reply, val); + sprintf(reply, "> %s", val); } else { - strcpy(reply, "can't find custom var"); + strcpy(reply, "null"); } } else if (memcmp(command, "sensor set ", 11) == 0) { - const char* args = &command[11]; - const char* value = strchr(args,' ') + 1; - char key [value-args+1]; - strncpy(key, args, value-args-1); + strcpy(tmp, &command[11]); + const char *parts[2]; + int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' '); + const char *key = (num > 0) ? parts[0] : ""; + const char *value = (num > 1) ? parts[1] : "null"; if (sensors.setSettingByKey(key, value)) { strcpy(reply, "ok"); } else { From f6064b41e9cdd5faac3a080d028640062b1aaa43 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 11 Oct 2025 18:00:57 +0200 Subject: [PATCH 013/409] gps_cli: set node location based on gps --- src/helpers/CommonCLI.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 6616a0562f..cc63cfeb75 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -580,6 +580,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch if (l != NULL) { l->syncTime(); } + } else if (memcmp(command, "gps setloc", 10) == 0) { + _prefs->node_lat = sensors.node_lat; + _prefs->node_lon = sensors.node_lon; + savePrefs(); + strcpy(reply, "ok"); } else if (memcmp(command, "gps", 3) == 0) { LocationProvider * l = sensors.getLocationProvider(); if (l != NULL) { From 4dc3dda2d8d13f2d76260589fed96e026b63eb5b Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 11 Oct 2025 18:32:02 +0200 Subject: [PATCH 014/409] xiao c3: migrated to esm, added missing roles, cleanup --- .../xiao_c3}/XiaoC3Board.h | 5 -- variants/xiao_c3/platformio.ini | 63 +++++++++++++++---- variants/xiao_c3/target.cpp | 9 ++- variants/xiao_c3/target.h | 7 +-- 4 files changed, 61 insertions(+), 23 deletions(-) rename {src/helpers => variants/xiao_c3}/XiaoC3Board.h (95%) diff --git a/src/helpers/XiaoC3Board.h b/variants/xiao_c3/XiaoC3Board.h similarity index 95% rename from src/helpers/XiaoC3Board.h rename to variants/xiao_c3/XiaoC3Board.h index c97f22b786..6ea1c15ffc 100644 --- a/src/helpers/XiaoC3Board.h +++ b/variants/xiao_c3/XiaoC3Board.h @@ -3,11 +3,6 @@ #include #include -// LoRa radio module pins for custom Seeduino XiaoC3 build -// #define P_LORA_SCLK D8 -// #define P_LORA_MISO D9 -// #define P_LORA_MOSI D10 - #include #include diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 1f27dfc88d..617f610e82 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -3,6 +3,8 @@ extends = esp32_base board = seeed_xiao_esp32c3 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} + -UENV_INCLUDE_GPS -I variants/xiao_c3 -D ESP32_CPU_FREQ=80 -D PIN_VBAT_READ=D0 @@ -12,23 +14,27 @@ build_flags = -D P_LORA_BUSY=D3 -D PIN_BOARD_SDA=D6 -D PIN_BOARD_SCL=D7 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 -D SX126X_RXEN=D5 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_c3> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} -[env:Xiao_C3_sx1262_repeater] +[env:Xiao_C3_repeater] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<../examples/simple_repeater/*.cpp> build_flags = ${Xiao_esp32_C3.build_flags} - -D RADIO_CLASS=CustomSX1262 - -D WRAPPER_CLASS=CustomSX1262Wrapper - -D SX126X_RX_BOOSTED_GAIN=1 - -D LORA_TX_POWER=22 -D ADVERT_NAME='"Xiao C3 Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -41,6 +47,24 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Xiao_C3_room_server] +extends = Xiao_esp32_C3 +build_src_filter = ${Xiao_esp32_C3.build_src_filter} + +<../examples/simple_room_server/*.cpp> +build_flags = + ${Xiao_esp32_C3.build_flags} + -D ADVERT_NAME='"Xiao C3 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 +lib_deps = + ${Xiao_esp32_C3.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + [env:Xiao_C3_companion_radio_ble] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} @@ -48,10 +72,6 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} + build_flags = ${Xiao_esp32_C3.build_flags} - -D RADIO_CLASS=CustomSX1262 - -D WRAPPER_CLASS=CustomSX1262Wrapper - -D SX126X_RX_BOOSTED_GAIN=1 - -D LORA_TX_POWER=22 -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -71,10 +91,6 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} + build_flags = ${Xiao_esp32_C3.build_flags} - -D RADIO_CLASS=CustomSX1262 - -D WRAPPER_CLASS=CustomSX1262Wrapper - -D SX126X_RX_BOOSTED_GAIN=1 - -D LORA_TX_POWER=22 -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D OFFLINE_QUEUE_SIZE=256 @@ -85,3 +101,24 @@ lib_deps = ${Xiao_esp32_C3.lib_deps} ${esp32_ota.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Xiao_C3_companion_radio_wifi] +extends = Xiao_esp32_C3 +build_src_filter = ${Xiao_esp32_C3.build_src_filter} + +<../examples/companion_radio/*.cpp> + + +build_flags = + ${Xiao_esp32_C3.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + ; -D BLE_DEBUG_LOGGING=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_esp32_C3.lib_deps} + ${esp32_ota.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_c3/target.cpp b/variants/xiao_c3/target.cpp index b3701ca7af..fe3f7196a0 100644 --- a/variants/xiao_c3/target.cpp +++ b/variants/xiao_c3/target.cpp @@ -14,7 +14,14 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif bool radio_init() { fallback_clock.begin(); diff --git a/variants/xiao_c3/target.h b/variants/xiao_c3/target.h index fa29e52bdb..a7ef442180 100644 --- a/variants/xiao_c3/target.h +++ b/variants/xiao_c3/target.h @@ -3,16 +3,15 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include #include -#include #include -#include +#include extern XiaoC3Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); From bf1da43d7dd6395badfe81f57e29f0403e5bcb27 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 11 Oct 2025 19:00:02 +0200 Subject: [PATCH 015/409] gps_cli: gps advert to control advert location policy --- examples/simple_repeater/MyMesh.cpp | 12 ++++++-- examples/simple_room_server/MyMesh.cpp | 12 ++++++-- examples/simple_sensor/SensorMesh.cpp | 12 ++++++-- src/helpers/CommonCLI.cpp | 39 ++++++++++++++++++++++++-- src/helpers/CommonCLI.h | 5 ++++ 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index f7153da35d..de8072f1dd 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -289,8 +289,16 @@ mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); + if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + app_data_len = builder.encodeTo(app_data); + } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + app_data_len = builder.encodeTo(app_data); + } else { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } } return createAdvert(self_id, app_data, app_data_len); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index d9a3639766..0073682065 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -116,8 +116,16 @@ mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); + if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + app_data_len = builder.encodeTo(app_data); + } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + app_data_len = builder.encodeTo(app_data); + } else { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } } return createAdvert(self_id, app_data, app_data_len); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 00da006aa6..97d95d5fda 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -241,8 +241,16 @@ mesh::Packet* SensorMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; { - AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); + if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + app_data_len = builder.encodeTo(app_data); + } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + app_data_len = builder.encodeTo(app_data); + } else { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } } return createAdvert(self_id, app_data, app_data_len); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index cc63cfeb75..757180f80d 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -67,7 +67,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read(pad, 4); // 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 - // 161 + if (file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)) == -1) { + _prefs->advert_loc_policy = ADVERT_LOC_PREFS; // default value + } // 161 + // 162 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -89,6 +92,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); + _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2); file.close(); } @@ -142,7 +146,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write(pad, 4); // 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 - // 161 + file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 + // 162 file.close(); } @@ -585,6 +590,36 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->node_lon = sensors.node_lon; savePrefs(); strcpy(reply, "ok"); + } else if (memcmp(command, "gps advert", 10) == 0) { + if (strlen(command) == 10) { + switch (_prefs->advert_loc_policy) { + case ADVERT_LOC_NONE: + strcpy(reply, "> none"); + break; + case ADVERT_LOC_PREFS: + strcpy(reply, "> prefs"); + break; + case ADVERT_LOC_SHARE: + strcpy(reply, "> share"); + break; + default: + strcpy(reply, "error"); + } + } else if (memcmp(command+11, "none", 4) == 0) { + _prefs->advert_loc_policy = ADVERT_LOC_NONE; + savePrefs(); + strcpy(reply, "ok"); + } else if (memcmp(command+11, "share", 5) == 0) { + _prefs->advert_loc_policy = ADVERT_LOC_SHARE; + savePrefs(); + strcpy(reply, "ok"); + } else if (memcmp(command+11, "prefs", 4) == 0) { + _prefs->advert_loc_policy = ADVERT_LOC_PREFS; + savePrefs(); + strcpy(reply, "ok"); + } else { + strcpy(reply, "error"); + } } else if (memcmp(command, "gps", 3) == 0) { LocationProvider * l = sensors.getLocationProvider(); if (l != NULL) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 07523643f3..684899130a 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -8,6 +8,10 @@ #define WITH_BRIDGE #endif +#define ADVERT_LOC_NONE 0 +#define ADVERT_LOC_SHARE 1 +#define ADVERT_LOC_PREFS 2 + struct NodePrefs { // persisted to file float airtime_factor; char node_name[32]; @@ -41,6 +45,7 @@ struct NodePrefs { // persisted to file // Gps settings uint8_t gps_enabled; uint32_t gps_interval; // in seconds + uint8_t advert_loc_policy; }; class CommonCLICallbacks { From c4a2b139308ecd3a03028fb2083a159311624b2e Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 11 Oct 2025 21:52:48 +0200 Subject: [PATCH 016/409] moved HeltecV3Board.h to variant folder --- .../heltec_v3}/HeltecV3Board.h | 18 +++--------------- variants/heltec_v3/platformio.ini | 7 +++++++ variants/heltec_v3/target.h | 2 +- 3 files changed, 11 insertions(+), 16 deletions(-) rename {src/helpers => variants/heltec_v3}/HeltecV3Board.h (88%) diff --git a/src/helpers/HeltecV3Board.h b/variants/heltec_v3/HeltecV3Board.h similarity index 88% rename from src/helpers/HeltecV3Board.h rename to variants/heltec_v3/HeltecV3Board.h index c63ed2d879..afdaf6398a 100644 --- a/src/helpers/HeltecV3Board.h +++ b/variants/heltec_v3/HeltecV3Board.h @@ -2,16 +2,7 @@ #include #include - -// LoRa radio module pins for Heltec V3 -// Also for Heltec Wireless Tracker/Paper -#define P_LORA_DIO_1 14 -#define P_LORA_NSS 8 -#define P_LORA_RESET RADIOLIB_NC -#define P_LORA_BUSY 13 -#define P_LORA_SCLK 9 -#define P_LORA_MISO 11 -#define P_LORA_MOSI 10 +#include // built-ins #ifndef PIN_VBAT_READ // set in platformio.ini for boards like Heltec Wireless Paper (20) @@ -22,9 +13,6 @@ #endif #define PIN_ADC_CTRL_ACTIVE LOW #define PIN_ADC_CTRL_INACTIVE HIGH -//#define PIN_LED_BUILTIN 35 - -#include "ESP32Board.h" #include @@ -43,7 +31,7 @@ class HeltecV3Board : public ESP32Board { // Auto-detect correct ADC_CTRL pin polarity (different for boards >3.2) pinMode(PIN_ADC_CTRL, INPUT); adc_active_state = !digitalRead(PIN_ADC_CTRL); - + pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive @@ -64,7 +52,7 @@ class HeltecV3Board : public ESP32Board { void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index b43c08700f..dcf566b32e 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -7,6 +7,13 @@ build_flags = -I variants/heltec_v3 -D HELTEC_LORA_V3 -D ESP32_CPU_FREQ=80 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/heltec_v3/target.h b/variants/heltec_v3/target.h index b212566407..739aecfe0d 100644 --- a/variants/heltec_v3/target.h +++ b/variants/heltec_v3/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include #include #include #include From 1979517381ecee7916e1d724a900e3c2c13c246b Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 11 Oct 2025 23:35:10 +0200 Subject: [PATCH 017/409] heltec v2 cleanup --- .../heltec_v2}/HeltecV2Board.h | 14 +------ variants/heltec_v2/platformio.ini | 38 +++++++++++++++++-- variants/heltec_v2/target.h | 2 +- 3 files changed, 37 insertions(+), 17 deletions(-) rename {src/helpers => variants/heltec_v2}/HeltecV2Board.h (85%) diff --git a/src/helpers/HeltecV2Board.h b/variants/heltec_v2/HeltecV2Board.h similarity index 85% rename from src/helpers/HeltecV2Board.h rename to variants/heltec_v2/HeltecV2Board.h index 5bf78778b2..a6221036dd 100644 --- a/src/helpers/HeltecV2Board.h +++ b/variants/heltec_v2/HeltecV2Board.h @@ -1,22 +1,12 @@ #pragma once #include - -// LoRa radio module pins for Heltec V2 -#define P_LORA_DIO_1 26 // DIO0 -#define P_LORA_NSS 18 -#define P_LORA_RESET RADIOLIB_NC // 14 -#define P_LORA_BUSY RADIOLIB_NC -#define P_LORA_SCLK 5 -#define P_LORA_MISO 19 -#define P_LORA_MOSI 27 +#include // built-ins #define PIN_VBAT_READ 37 #define PIN_LED_BUILTIN 25 -#include "ESP32Board.h" - #include class HeltecV2Board : public ESP32Board { @@ -39,7 +29,7 @@ class HeltecV2Board : public ESP32Board { void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 100053ede7..539cc52e83 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -7,13 +7,20 @@ build_flags = -D HELTEC_LORA_V2 -D RADIO_CLASS=CustomSX1276 -D WRAPPER_CLASS=CustomSX1276Wrapper + -D P_LORA_DIO_1=26 + -D P_LORA_NSS=18 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=RADIOLIB_NC + -D P_LORA_SCLK=5 + -D P_LORA_MISO=19 + -D P_LORA_MOSI=27 + -D P_LORA_TX_LED=25 -D SX127X_CURRENT_LIMIT=120 -D LORA_TX_POWER=20 -D PIN_BOARD_SDA=4 -D PIN_BOARD_SCL=15 -D PIN_USER_BTN=0 -D PIN_OLED_RESET=16 - -D P_LORA_TX_LED=25 build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_v2> lib_deps = @@ -112,7 +119,7 @@ lib_deps = extends = Heltec_lora32_v2 build_flags = ${Heltec_lora32_v2.build_flags} - -D MAX_CONTACTS=170 + -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -128,7 +135,7 @@ build_flags = ${Heltec_lora32_v2.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=170 + -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 @@ -148,7 +155,7 @@ build_flags = ${Heltec_lora32_v2.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=170 + -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 @@ -164,3 +171,26 @@ build_src_filter = ${Heltec_lora32_v2.build_src_filter} lib_deps = ${Heltec_lora32_v2.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Heltec_v2_companion_radio_wifi] +extends = Heltec_lora32_v2 +build_flags = + ${Heltec_lora32_v2.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=160 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_lora32_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_v2/target.h b/variants/heltec_v2/target.h index 2e5b17de1d..48d750be44 100644 --- a/variants/heltec_v2/target.h +++ b/variants/heltec_v2/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include #include #include #include From 7cb2e0863a8171a300c062547d95697e1c69f009 Mon Sep 17 00:00:00 2001 From: recrof Date: Sun, 12 Oct 2025 00:13:34 +0200 Subject: [PATCH 018/409] move StationG2Board.h to variants, enable ESM, add companion wifi, cleanup --- .../station_g2}/StationG2Board.h | 17 +-------- variants/station_g2/platformio.ini | 36 +++++++++++++++++-- variants/station_g2/target.cpp | 9 ++++- variants/station_g2/target.h | 6 ++-- 4 files changed, 45 insertions(+), 23 deletions(-) rename {src/helpers => variants/station_g2}/StationG2Board.h (83%) diff --git a/src/helpers/StationG2Board.h b/variants/station_g2/StationG2Board.h similarity index 83% rename from src/helpers/StationG2Board.h rename to variants/station_g2/StationG2Board.h index 2e31571f56..a905682c8d 100644 --- a/src/helpers/StationG2Board.h +++ b/variants/station_g2/StationG2Board.h @@ -1,22 +1,7 @@ #pragma once #include - -// LoRa radio module pins for Station G2 -#define P_LORA_DIO_1 48 -#define P_LORA_NSS 11 -#define P_LORA_RESET 21 -#define P_LORA_BUSY 47 -#define P_LORA_SCLK 12 -#define P_LORA_MISO 14 -#define P_LORA_MOSI 13 - -// built-ins -//#define PIN_LED_BUILTIN 35 -//#define PIN_VEXT_EN 36 - -#include "ESP32Board.h" - +#include #include class StationG2Board : public ESP32Board { diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 016a46a958..bda8b7aea5 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -3,28 +3,40 @@ extends = esp32_base board = station-g2 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} -I variants/station_g2 + -I src/helpers/ui -D STATION_G2 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=48 + -D P_LORA_NSS=11 + -D P_LORA_RESET=21 + -D P_LORA_BUSY=47 + -D P_LORA_SCLK=12 + -D P_LORA_MISO=14 + -D P_LORA_MOSI=13 -D LORA_TX_POWER=19 ; -D P_LORA_TX_LED=35 -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=38 + -D PIN_GPS_RX=7 + -D PIN_GPS_TX=15 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 ; -D SX126X_RX_BOOSTED_GAIN=1 - DO NOT ENABLE THIS! ; https://wiki.uniteng.com/en/meshtastic/station-g2#impact-of-lora-node-dense-areashigh-noise-environments-on-rf-performance - -I src/helpers/ui -D DISPLAY_CLASS=SH1106Display build_src_filter = ${esp32_base.build_src_filter} +<../variants/station_g2> + + + + lib_deps = ${esp32_base.lib_deps} + ${sensor_base.lib_deps} adafruit/Adafruit SH110X @ ~2.1.13 adafruit/Adafruit GFX Library @ ^1.12.1 @@ -172,7 +184,6 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=SH1106Display -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 @@ -190,7 +201,6 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=SH1106Display -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -205,3 +215,23 @@ build_src_filter = ${Station_G2.build_src_filter} lib_deps = ${Station_G2.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Station_G2_companion_radio_wifi] +extends = Station_G2 +build_flags = + ${Station_G2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Station_G2.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Station_G2.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 5423af68ab..c2ee97e239 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -14,7 +14,14 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/station_g2/target.h b/variants/station_g2/target.h index 3f67af3a5d..2bf7016d4e 100644 --- a/variants/station_g2/target.h +++ b/variants/station_g2/target.h @@ -3,10 +3,10 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include #include #include -#include +#include #ifdef DISPLAY_CLASS #include @@ -16,7 +16,7 @@ extern StationG2Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 69cddbca4e66e74fe19e71994360872a6a6a96eb Mon Sep 17 00:00:00 2001 From: recrof Date: Sun, 12 Oct 2025 00:32:26 +0200 Subject: [PATCH 019/409] move LilyGoTLoraBoard.h to variants, use template in platformio.ini, cleanup --- .../lilygo_tlora_v2_1}/LilyGoTLoraBoard.h | 4 ++-- variants/lilygo_tlora_v2_1/platformio.ini | 22 ++++--------------- variants/lilygo_tlora_v2_1/target.h | 3 +-- 3 files changed, 7 insertions(+), 22 deletions(-) rename {src/helpers => variants/lilygo_tlora_v2_1}/LilyGoTLoraBoard.h (93%) diff --git a/src/helpers/LilyGoTLoraBoard.h b/variants/lilygo_tlora_v2_1/LilyGoTLoraBoard.h similarity index 93% rename from src/helpers/LilyGoTLoraBoard.h rename to variants/lilygo_tlora_v2_1/LilyGoTLoraBoard.h index c595740fd6..545219b2bc 100644 --- a/src/helpers/LilyGoTLoraBoard.h +++ b/variants/lilygo_tlora_v2_1/LilyGoTLoraBoard.h @@ -1,7 +1,7 @@ #pragma once #include -#include "ESP32Board.h" +#include // LILYGO T-LoRa V2.1-1.6 board with SX1276 class LilyGoTLoraBoard : public ESP32Board { @@ -9,7 +9,7 @@ class LilyGoTLoraBoard : public ESP32Board { const char* getManufacturerName() const override { return "LILYGO T-LoRa V2.1-1.6"; } - + uint16_t getBattMilliVolts() override { analogReadResolution(12); diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index e580959cab..9ef9734e59 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -6,6 +6,8 @@ build_type = release ; Set build type to release board_build.partitions = min_spiffs.csv ; get around 4mb flash limit build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} + -UENV_INCLUDE_GPS -I variants/lilygo_tlora_v2_1 -Os -ffunction-sections -fdata-sections ; Optimize for size -D LILYGO_TLORA ; LILYGO T-LoRa V2.1-1.6 ESP32 with SX1276 @@ -27,28 +29,18 @@ build_flags = -D WRAPPER_CLASS=CustomSX1276Wrapper -D SX127X_CURRENT_LIMIT=120 -D LORA_TX_POWER=20 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_BMP280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 build_src_filter = ${esp32_base.build_src_filter} + + +<../variants/lilygo_tlora_v2_1> + lib_deps = ${esp32_base.lib_deps} - adafruit/Adafruit SSD1306 @ ^2.5.13 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit BMP280 Library @ ^2.6.8 + ${sensor_base.lib_deps} ; === LILYGO T-LoRa V2.1-1.6 with SX1276 environments === [env:LilyGo_TLora_V2_1_1_6_repeater] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - + +<../examples/simple_repeater> build_flags = ${LilyGo_TLora_V2_1_1_6.build_flags} @@ -73,7 +65,6 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - + +<../examples/simple_repeater> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} @@ -89,7 +80,6 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -111,7 +101,6 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + - + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -149,7 +138,6 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + - + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -163,7 +151,6 @@ lib_deps = [env:LilyGo_TLora_V2_1_1_6_repeater_bridge_rs232] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - + + +<../examples/simple_repeater> build_flags = @@ -187,7 +174,6 @@ lib_deps = [env:LilyGo_TLora_V2_1_1_6_repeater_bridge_espnow] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - + + +<../examples/simple_repeater> build_flags = diff --git a/variants/lilygo_tlora_v2_1/target.h b/variants/lilygo_tlora_v2_1/target.h index 380d733b40..326a0dee73 100644 --- a/variants/lilygo_tlora_v2_1/target.h +++ b/variants/lilygo_tlora_v2_1/target.h @@ -3,10 +3,9 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include #include #include -#include #include #ifdef DISPLAY_CLASS #include From 837e7dcbdbbb08f0ba0514fdac6a5ed056dd2b67 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 12 Oct 2025 12:33:20 +1100 Subject: [PATCH 020/409] * Advert type fix * GPS pref defaults tidy --- examples/simple_repeater/MyMesh.cpp | 1 + examples/simple_room_server/MyMesh.cpp | 11 ++++++++--- examples/simple_sensor/SensorMesh.cpp | 11 ++++++++--- src/helpers/CommonCLI.cpp | 4 +--- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index de8072f1dd..eaf0fb2b9a 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -645,6 +645,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc // GPS defaults _prefs.gps_enabled = 0; _prefs.gps_interval = 0; + _prefs.advert_loc_policy = ADVERT_LOC_PREFS; } void MyMesh::begin(FILESYSTEM *fs) { diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 0073682065..b241788e4a 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -117,13 +117,13 @@ mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data_len; { if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name); app_data_len = builder.encodeTo(app_data); } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, sensors.node_lat, sensors.node_lon); app_data_len = builder.encodeTo(app_data); } else { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); app_data_len = builder.encodeTo(app_data); } } @@ -620,6 +620,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif + // GPS defaults + _prefs.gps_enabled = 0; + _prefs.gps_interval = 0; + _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + next_post_idx = 0; next_client_idx = 0; next_push = 0; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 97d95d5fda..d365f7fa28 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -242,13 +242,13 @@ mesh::Packet* SensorMesh::createSelfAdvert() { uint8_t app_data_len; { if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name); app_data_len = builder.encodeTo(app_data); } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, sensors.node_lat, sensors.node_lon); app_data_len = builder.encodeTo(app_data); } else { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); app_data_len = builder.encodeTo(app_data); } } @@ -690,6 +690,11 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.disable_fwd = true; _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled + + // GPS defaults + _prefs.gps_enabled = 0; + _prefs.gps_interval = 0; + _prefs.advert_loc_policy = ADVERT_LOC_PREFS; } void SensorMesh::begin(FILESYSTEM* fs) { diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 757180f80d..b0229f70e9 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -67,9 +67,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read(pad, 4); // 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 - if (file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)) == -1) { - _prefs->advert_loc_policy = ADVERT_LOC_PREFS; // default value - } // 161 + file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 // 162 // sanitise bad pref values From 93c01807400db0a04fbef2aa2762fa6a717061c6 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 12 Oct 2025 12:49:26 +1100 Subject: [PATCH 021/409] * Refactor: advert_loc_policy now applied in new method CommonCLI::buildAdvertData() --- examples/simple_repeater/MyMesh.cpp | 14 +------------- examples/simple_room_server/MyMesh.cpp | 14 +------------- examples/simple_sensor/SensorMesh.cpp | 14 +------------- src/helpers/CommonCLI.cpp | 14 ++++++++++++++ src/helpers/CommonCLI.h | 1 + 5 files changed, 18 insertions(+), 39 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index eaf0fb2b9a..6c85b7e09f 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -287,19 +287,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); - app_data_len = builder.encodeTo(app_data); - } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); - app_data_len = builder.encodeTo(app_data); - } else { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - } + uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_REPEATER, app_data); return createAdvert(self_id, app_data, app_data_len); } diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index b241788e4a..e4b57f98c4 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -114,19 +114,7 @@ bool MyMesh::processAck(const uint8_t *data) { mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name); - app_data_len = builder.encodeTo(app_data); - } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, sensors.node_lat, sensors.node_lon); - app_data_len = builder.encodeTo(app_data); - } else { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - } + uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_ROOM, app_data); return createAdvert(self_id, app_data, app_data_len); } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index d365f7fa28..92ea188930 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -239,19 +239,7 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint mesh::Packet* SensorMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name); - app_data_len = builder.encodeTo(app_data); - } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, sensors.node_lat, sensors.node_lon); - app_data_len = builder.encodeTo(app_data); - } else { - AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - } + uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_SENSOR, app_data); return createAdvert(self_id, app_data, app_data_len); } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b0229f70e9..87f20f5ad6 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -1,6 +1,7 @@ #include #include "CommonCLI.h" #include "TxtDataHelpers.h" +#include "AdvertDataHelpers.h" #include // Believe it or not, this std C function is busted on some platforms! @@ -160,6 +161,19 @@ void CommonCLI::savePrefs() { _callbacks->savePrefs(); } +uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) { + if (_prefs->advert_loc_policy == ADVERT_LOC_NONE) { + AdvertDataBuilder builder(node_type, _prefs->node_name); + return builder.encodeTo(app_data); + } else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) { + AdvertDataBuilder builder(node_type, _prefs->node_name, sensors.node_lat, sensors.node_lon); + return builder.encodeTo(app_data); + } else { + AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon); + return builder.encodeTo(app_data); + } +} + void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 684899130a..ce56701648 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -98,4 +98,5 @@ class CommonCLI { void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); void handleCommand(uint32_t sender_timestamp, const char* command, char* reply); + uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data); }; From 8426fddcb7af7243f97c665067e6ed4d03910f97 Mon Sep 17 00:00:00 2001 From: Woodie-07 Date: Sun, 12 Oct 2025 16:09:57 +0100 Subject: [PATCH 022/409] workaround for LR1110 shift issue it seems that if the LR1110 radio hears a packet corrupted in a specific way, it'll report a packet of 0 length and with the header error IRQ set. every packet received afterwards will then be shifted to the right by 4 bytes on top of the radio's reported offset. this can occur multiple times with the shift increasing by 4 bytes each time. thus, this patch will read from an additional offset after hearing the trigger packet. transmitting seems to reset the shift - unsure exactly what operation resets it but standby() is called after tx so patch assumes shift is 0 after standby(). more investigation may be needed here. --- platformio.ini | 2 +- src/helpers/radiolib/CustomLR1110.h | 68 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 4fe17af939..e72b7b3a09 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,7 +18,7 @@ monitor_speed = 115200 lib_deps = SPI Wire - jgromes/RadioLib @ ^7.1.2 + jgromes/RadioLib @ ^7.3.0 rweather/Crypto @ ^0.4.0 adafruit/RTClib @ ^2.1.3 melopero/Melopero RV3028 @ ^1.1.0 diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index e82f48f5c7..0d1fbccc10 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -9,6 +9,74 @@ class CustomLR1110 : public LR1110 { public: CustomLR1110(Module *mod) : LR1110(mod) { } + uint8_t shiftCount = 0; + + int16_t standby() override { + // tx resets the shift, standby is called on tx completion + // this might not actually be what resets it, but it seems to work + // more investigation needed + this->shiftCount = 0; + return LR1110::standby(); + } + + size_t getPacketLength(bool update) override { + size_t len = LR1110::getPacketLength(update); + if (len == 0) { + uint32_t irq = getIrqStatus(); + if (irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { + Serial.println(F("got possible bug packet")); + this->shiftCount += 4; // uint8 will loop around to 0 at 256, perfect as rx buffer is 256 bytes + } else { + Serial.println(F("got zero-length packet without header err irq")); + } + } + return len; + } + + int16_t readData(uint8_t *data, size_t len) override { + // check active modem + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if((modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) && + (modem != RADIOLIB_LR11X0_PACKET_TYPE_GFSK)) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check integrity CRC + uint32_t irq = getIrqStatus(); + int16_t crcState = RADIOLIB_ERR_NONE; + // Report CRC mismatch when there's a payload CRC error, or a header error and no valid header (to avoid false alarm from previous packet) + if((irq & RADIOLIB_LR11X0_IRQ_CRC_ERR) || ((irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID))) { + crcState = RADIOLIB_ERR_CRC_MISMATCH; + } + + // get packet length + // the offset is needed since LR11x0 seems to move the buffer base by 4 bytes on every packet + uint8_t offset = 0; + size_t length = LR1110::getPacketLength(true, &offset); + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + length = len; + } + + // read packet data + state = readBuffer8(data, length, (uint8_t)(offset + this->shiftCount)); // add shiftCount to offset - only change from radiolib + RADIOLIB_ASSERT(state); + + // clear the Rx buffer + state = clearRxBuffer(); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqState(RADIOLIB_LR11X0_IRQ_ALL); + + // check if CRC failed - this is done after reading data to give user the option to keep them + RADIOLIB_ASSERT(crcState); + + return(state); + } + RadioLibTime_t getTimeOnAir(size_t len) override { // calculate number of symbols float N_symbol = 0; From c6e5d5021eb9592ebaf47a4f45cae031bea92d97 Mon Sep 17 00:00:00 2001 From: recrof Date: Sun, 12 Oct 2025 17:16:45 +0200 Subject: [PATCH 023/409] fix: remove VL53L0X because it causes bootloops on esp32c3 --- variants/xiao_c3/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 617f610e82..683199d93a 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -5,6 +5,7 @@ build_flags = ${esp32_base.build_flags} ${sensor_base.build_flags} -UENV_INCLUDE_GPS + -UENV_INCLUDE_VL53L0X -I variants/xiao_c3 -D ESP32_CPU_FREQ=80 -D PIN_VBAT_READ=D0 From c6b4a584498ea375e6227d405b65fe4c67735017 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 14 Oct 2025 12:31:43 +1100 Subject: [PATCH 024/409] * repeater and room server: enable downgrading permissions on guest login --- examples/simple_repeater/MyMesh.cpp | 1 + examples/simple_room_server/MyMesh.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6c85b7e09f..4bb9a2bbe2 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -114,6 +114,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr MESH_DEBUG_PRINTLN("Login success!"); client->last_timestamp = sender_timestamp; client->last_activity = getRTCClock()->getCurrentTime(); + client->permissions &= ~0x03; client->permissions |= perms; memcpy(client->shared_secret, secret, PUB_KEY_SIZE); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index e4b57f98c4..c1a39a11fc 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -286,7 +286,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator ClientInfo* client = NULL; - if (data[8] == 0 && !_prefs.allow_read_only) { // blank password, just check if sender is in ACL + if (data[8] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { #if MESH_DEBUG @@ -322,6 +322,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m client->extra.room.push_failures = 0; client->last_activity = getRTCClock()->getCurrentTime(); + client->permissions &= ~0x03; client->permissions |= perm; memcpy(client->shared_secret, secret, PUB_KEY_SIZE); From fa8c31be883be52066eba131b339676e72f8ccb1 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 15 Oct 2025 22:47:55 +1100 Subject: [PATCH 025/409] * fix for RAK12500 GPS (I2C) --- .../sensors/EnvironmentSensorManager.cpp | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index aa51c85a20..e7a9f5e5c4 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -98,6 +98,41 @@ static bool serialGPSFlag = false; static SFE_UBLOX_GNSS ublox_GNSS; #endif +class RAK12500LocationProvider : public LocationProvider { + long _lat = 0; + long _lng = 0; + long _alt = 0; + int _sats = 0; + long _epoch = 0; + bool _fix = false; +public: + long getLatitude() override { return _lat; } + long getLongitude() override { return _lng; } + long getAltitude() override { return _alt; } + long satellitesCount() override { return _sats; } + bool isValid() override { return _fix; } + long getTimestamp() override { return _epoch; } + void sendSentence(const char * sentence) override { } + void reset() override { } + void begin() override { } + void stop() override { } + void loop() override { + if (ublox_GNSS.getGnssFixOk(8)) { + _fix = true; + _lat = ublox_GNSS.getLatitude(2) / 10; + _lng = ublox_GNSS.getLongitude(2) / 10; + _alt = ublox_GNSS.getAltitude(2); + _sats = ublox_GNSS.getSIV(2); + } else { + _fix = false; + } + _epoch = ublox_GNSS.getUnixEpoch(2); + } + bool isEnabled() override { return true; } +}; + +static RAK12500LocationProvider RAK12500_provider; + bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_GPS #ifdef RAK_WISBLOCK_GPS @@ -521,12 +556,22 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ //Try to init RAK12500 on I2C if (ublox_GNSS.begin(Wire) == true){ MESH_DEBUG_PRINTLN("RAK12500 GPS init correctly with pin %i",ioPin); - ublox_GNSS.setI2COutput(COM_TYPE_NMEA); + ublox_GNSS.setI2COutput(COM_TYPE_UBX); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GALILEO); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GLONASS); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_SBAS); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_BEIDOU); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_IMES); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_QZSS); + ublox_GNSS.setMeasurementRate(1000); ublox_GNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); gpsResetPin = ioPin; i2cGPSFlag = true; gps_active = true; gps_detected = true; + + _location = &RAK12500_provider; return true; } else if(Serial1){ @@ -583,14 +628,7 @@ void EnvironmentSensorManager::loop() { if (millis() > next_gps_update) { if(gps_active){ #ifdef RAK_WISBLOCK_GPS - if(i2cGPSFlag){ - node_lat = ((double)ublox_GNSS.getLatitude())/10000000.; - node_lon = ((double)ublox_GNSS.getLongitude())/10000000.; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); - node_altitude = ((double)ublox_GNSS.getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); - } - else if (serialGPSFlag && _location->isValid()) { + if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); From d3be6afccb83a4a7f15a64aaded6c6e75c2d9cbe Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 15 Oct 2025 22:51:05 +1100 Subject: [PATCH 026/409] * fix for non-RAK targets --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index e7a9f5e5c4..2954589417 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -96,7 +96,6 @@ static bool serialGPSFlag = false; #define TELEM_RAK12500_ADDRESS 0x42 //RAK12500 Ublox GPS via i2c #include static SFE_UBLOX_GNSS ublox_GNSS; -#endif class RAK12500LocationProvider : public LocationProvider { long _lat = 0; @@ -132,6 +131,7 @@ class RAK12500LocationProvider : public LocationProvider { }; static RAK12500LocationProvider RAK12500_provider; +#endif bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_GPS From cd920693ec4c21f184c1512162c578f5fc119d16 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 16 Oct 2025 17:33:22 +1100 Subject: [PATCH 027/409] * UITask: new UI_HAS_JOYSTICK * MomentaryButton: new constructor 'multiclick' param * WIoTrackerL1: now just use joystick, joystick press for KEY_ENTER, no multi-click for snappier UI --- examples/companion_radio/ui-new/UITask.cpp | 31 ++++++++++++++-------- src/helpers/ui/MomentaryButton.cpp | 4 +-- src/helpers/ui/MomentaryButton.h | 2 +- variants/wio-tracker-l1/platformio.ini | 2 ++ variants/wio-tracker-l1/target.cpp | 7 ++--- variants/wio-tracker-l1/target.h | 1 + variants/wio-tracker-l1/variant.h | 3 ++- 7 files changed, 32 insertions(+), 18 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index c3da064339..b6484a0011 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -20,7 +20,11 @@ #define UI_RECENT_LIST_SIZE 4 #endif -#define PRESS_LABEL "long press" +#if UI_HAS_JOYSTICK + #define PRESS_LABEL "press Enter" +#else + #define PRESS_LABEL "long press" +#endif #include "icons.h" @@ -360,7 +364,7 @@ class HomeScreen : public UIScreen { display.drawTextCentered(display.width() / 2, 34, "hibernating..."); } else { display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); - display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate: " PRESS_LABEL); + display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL); } } return 5000; // next render after 5000 ms @@ -660,19 +664,13 @@ bool UITask::isButtonPressed() const { void UITask::loop() { char c = 0; -#if defined(PIN_USER_BTN) +#if UI_HAS_JOYSTICK int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_NEXT); + c = checkDisplayOn(KEY_ENTER); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); - } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { - c = handleDoubleClick(KEY_PREV); - } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code } -#endif -#if defined(WIO_TRACKER_L1) ev = joystick_left.check(); if (ev == BUTTON_EVENT_CLICK) { c = checkDisplayOn(KEY_LEFT); @@ -685,6 +683,17 @@ void UITask::loop() { } else if (ev == BUTTON_EVENT_LONG_PRESS) { c = handleLongPress(KEY_RIGHT); } +#elif defined(PIN_USER_BTN) + int ev = user_btn.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_NEXT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_ENTER); + } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { + c = handleDoubleClick(KEY_PREV); + } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { + c = handleTripleClick(KEY_SELECT); + } #endif #if defined(PIN_USER_BTN_ANA) ev = analog_btn.check(); diff --git a/src/helpers/ui/MomentaryButton.cpp b/src/helpers/ui/MomentaryButton.cpp index 0ea4b027a2..9d01e5b011 100644 --- a/src/helpers/ui/MomentaryButton.cpp +++ b/src/helpers/ui/MomentaryButton.cpp @@ -2,7 +2,7 @@ #define MULTI_CLICK_WINDOW_MS 280 -MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup) { +MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup, bool multiclick) { _pin = pin; _reverse = reverse; _pull = pulldownup; @@ -13,7 +13,7 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse _threshold = 0; _click_count = 0; _last_click_time = 0; - _multi_click_window = MULTI_CLICK_WINDOW_MS; + _multi_click_window = multiclick ? MULTI_CLICK_WINDOW_MS : 0; _pending_click = false; } diff --git a/src/helpers/ui/MomentaryButton.h b/src/helpers/ui/MomentaryButton.h index 1122e56a57..358a343b0f 100644 --- a/src/helpers/ui/MomentaryButton.h +++ b/src/helpers/ui/MomentaryButton.h @@ -23,7 +23,7 @@ class MomentaryButton { bool isPressed(int level) const; public: - MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false); + MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false, bool multiclick=true); MomentaryButton(int8_t pin, int long_press_mills, int analog_threshold); void begin(); int check(bool repeat_click=false); // returns one of BUTTON_EVENT_* diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 971a32cb93..31c6bcb075 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -63,6 +63,7 @@ build_flags = ${WioTrackerL1.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SH1106Display + -D UI_HAS_JOYSTICK=1 -D OFFLINE_QUEUE_SIZE=256 -D PIN_BUZZER=12 -D QSPIFLASH=1 @@ -91,6 +92,7 @@ build_flags = ${WioTrackerL1.build_flags} -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SH1106Display + -D UI_HAS_JOYSTICK=1 -D PIN_BUZZER=12 -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 374e53afde..e7b73040bb 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -21,9 +21,10 @@ EnvironmentSensorManager sensors = EnvironmentSensorManager(); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; - MomentaryButton user_btn(PIN_USER_BTN, 1000, true); - MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true); - MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true); + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, false, false); + MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true, false, false); + MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true, false, false); + MomentaryButton back_btn(PIN_BACK_BTN, 1000, true, false, false); #endif bool radio_init() { diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index d3021d6df7..97e575d896 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -27,6 +27,7 @@ extern EnvironmentSensorManager sensors; extern MomentaryButton user_btn; extern MomentaryButton joystick_left; extern MomentaryButton joystick_right; + extern MomentaryButton back_btn; #endif bool radio_init(); diff --git a/variants/wio-tracker-l1/variant.h b/variants/wio-tracker-l1/variant.h index 7e1b1b8666..86ad62baa9 100644 --- a/variants/wio-tracker-l1/variant.h +++ b/variants/wio-tracker-l1/variant.h @@ -31,12 +31,13 @@ #define PIN_BUTTON4 (27) // Joystick Left #define PIN_BUTTON5 (28) // Joystick Right #define PIN_BUTTON6 (29) // Joystick Press -#define PIN_USER_BTN PIN_BUTTON1 +#define PIN_BACK_BTN PIN_BUTTON1 #define JOYSTICK_UP PIN_BUTTON2 #define JOYSTICK_DOWN PIN_BUTTON3 #define JOYSTICK_LEFT PIN_BUTTON4 #define JOYSTICK_RIGHT PIN_BUTTON5 #define JOYSTICK_PRESS PIN_BUTTON6 +#define PIN_USER_BTN PIN_BUTTON6 // Buzzer // #define PIN_BUZZER (12) // Buzzer pin (defined per firmware type) From 0e7486552dec3868ca2093917d65039bf43f8f72 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:17:23 +0200 Subject: [PATCH 028/409] Add simple BME680 support to RAK with adafruit library --- platformio.ini | 2 ++ .../sensors/EnvironmentSensorManager.cpp | 33 ++++++++++++++++++- .../sensors/EnvironmentSensorManager.h | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 4fe17af939..7af3374752 100644 --- a/platformio.ini +++ b/platformio.ini @@ -124,6 +124,7 @@ build_flags = -D ENV_INCLUDE_INA260=1 -D ENV_INCLUDE_MLX90614=1 -D ENV_INCLUDE_VL53L0X=1 + -D ENV_INCLUDE_BME680=1 lib_deps = adafruit/Adafruit INA3221 Library @ ^1.0.1 adafruit/Adafruit INA219 @ ^1.2.3 @@ -138,3 +139,4 @@ lib_deps = adafruit/Adafruit MLX90614 Library @ ^2.1.5 adafruit/Adafruit_VL53L0X @ ^1.2.4 stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit BME680 Library @ ^2.0.4 diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2954589417..e244ef209c 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -6,6 +6,14 @@ #define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors #endif +#ifdef ENV_INCLUDE_BME680 +#ifndef TELEM_BME680_ADDRESS +#define TELEM_BME680_ADDRESS 0x76 // BME680 environmental sensor I2C address +#endif +#include +static Adafruit_BME680 BME680; +#endif + #if ENV_INCLUDE_AHTX0 #define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address #include @@ -286,6 +294,16 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_BME680 + if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) { + MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); + BME680_initialized = true; + } else { + BME680_initialized = false; + MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); + } + #endif + return true; } @@ -415,6 +433,18 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_BME680 + if (BME680_initialized) { + if (BME680.performReading()) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); + telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); + next_available_channel++; + } + } + #endif + } return true; @@ -623,6 +653,7 @@ void EnvironmentSensorManager::stop_gps() { void EnvironmentSensorManager::loop() { static long next_gps_update = 0; + #if ENV_INCLUDE_GPS _location->loop(); if (millis() > next_gps_update) { @@ -647,5 +678,5 @@ void EnvironmentSensorManager::loop() { } next_gps_update = millis() + 1000; } + #endif } -#endif diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 09c6cae412..133d26504b 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -20,6 +20,7 @@ class EnvironmentSensorManager : public SensorManager { bool MLX90614_initialized = false; bool VL53L0X_initialized = false; bool SHT4X_initialized = false; + bool BME680_initialized = false; bool gps_detected = false; bool gps_active = false; From 3c48f016019d611d763493e0de246397d2a7ea98 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:29:22 +0200 Subject: [PATCH 029/409] BME680 library doesn't have altitude calculation, we can add it here to match other sensors' --- src/helpers/sensors/EnvironmentSensorManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index e244ef209c..bb70c0b5ca 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -8,8 +8,9 @@ #ifdef ENV_INCLUDE_BME680 #ifndef TELEM_BME680_ADDRESS -#define TELEM_BME680_ADDRESS 0x76 // BME680 environmental sensor I2C address +#define TELEM_BME680_ADDRESS 0x76 #endif +#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25) #include static Adafruit_BME680 BME680; #endif @@ -439,6 +440,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); next_available_channel++; } @@ -680,3 +682,4 @@ void EnvironmentSensorManager::loop() { } #endif } +#endif From 02351abc2d5cffbd68d6c940ee8ac974ce0605b2 Mon Sep 17 00:00:00 2001 From: Woodie-07 Date: Thu, 16 Oct 2025 16:25:18 +0100 Subject: [PATCH 030/409] change println to debug macro in lr1110 patch --- src/helpers/radiolib/CustomLR1110.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index 0d1fbccc10..f723bb7f0e 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -1,6 +1,7 @@ #pragma once #include +#include "MeshCore.h" #define LR1110_IRQ_HAS_PREAMBLE 0b0000000100 // 4 4 valid LoRa header received #define LR1110_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received @@ -24,10 +25,10 @@ class CustomLR1110 : public LR1110 { if (len == 0) { uint32_t irq = getIrqStatus(); if (irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { - Serial.println(F("got possible bug packet")); + MESH_DEBUG_PRINTLN("LR1110: got header err, assuming shift"); this->shiftCount += 4; // uint8 will loop around to 0 at 256, perfect as rx buffer is 256 bytes } else { - Serial.println(F("got zero-length packet without header err irq")); + MESH_DEBUG_PRINTLN("LR1110: got zero-length packet without header err irq"); } } return len; From 24ed5b377f35cea6e44e6512e4ffa2442dfb03f6 Mon Sep 17 00:00:00 2001 From: recrof Date: Fri, 17 Oct 2025 16:25:58 +0200 Subject: [PATCH 031/409] added custom pio task "Create UF2 file" --- create-uf2.py | 31 +++++++++++++++++++++++++++++++ platformio.ini | 1 + 2 files changed, 32 insertions(+) create mode 100644 create-uf2.py diff --git a/create-uf2.py b/create-uf2.py new file mode 100644 index 0000000000..10ec0ed634 --- /dev/null +++ b/create-uf2.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 + +# Adds PlatformIO post-processing to convert hex files to uf2 files + +import os + +Import("env") + +firmware_hex = "${BUILD_DIR}/${PROGNAME}.hex" +uf2_file = os.environ.get("UF2_FILE_PATH", "${BUILD_DIR}/${PROGNAME}.uf2") + +def create_uf2_action(source, target, env): + uf2_cmd = " ".join( + [ + '"$PYTHONEXE"', + '"$PROJECT_DIR/bin/uf2conv/uf2conv.py"', + '-f', '0xADA52840', + '-c', firmware_hex, + '-o', uf2_file, + ] + ) + env.Execute(uf2_cmd) + +env.AddCustomTarget( + name="create_uf2", + dependencies=firmware_hex, + actions=create_uf2_action, + title="Create UF2 file", + description="Use uf2conv to convert hex binary into uf2", + always_build=True, +) \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 4fe17af939..f107867c43 100644 --- a/platformio.ini +++ b/platformio.ini @@ -76,6 +76,7 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ [nrf52_base] extends = arduino_base platform = nordicnrf52 +extra_scripts = create-uf2.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM -D LFS_NO_ASSERT=1 From 006af527768f2aebffe3f12cb6dc4fb60cfd6fc7 Mon Sep 17 00:00:00 2001 From: haxwithaxe Date: Fri, 17 Oct 2025 14:20:55 -0400 Subject: [PATCH 032/409] Added more polished build.sh usage --- build.sh | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/build.sh b/build.sh index a871b9ab66..732dac43f7 100755 --- a/build.sh +++ b/build.sh @@ -1,12 +1,45 @@ #!/usr/bin/env bash -# usage -# sh build.sh build-firmware RAK_4631_Repeater -# sh build.sh build-firmwares -# sh build.sh build-matching-firmwares RAK_4631 -# sh build.sh build-companion-firmwares -# sh build.sh build-repeater-firmwares -# sh build.sh build-room-server-firmwares +global_usage() { + cat - < [target] + +Commands: + help|usage|-h|--help: Shows this message. + build-firmware : Build the firmware for the given build target. + build-firmwares: Build all firmwares for all targets. + build-matching-firmwares : Build all firmwares for build targets containing the string given for . + build-companion-firmwares: Build all companion firmwares for all build targets. + build-repeater-firmwares: Build all repeater firmwares for all build targets. + build-room-server-firmwares: Build all chat room server firmwares for all build targets. + +Examples: +Build firmware for the "RAK_4631_Repeater" device target +$ sh build.sh build-firmware RAK_4631_Repeater + +Build all firmwares for device targets containing the string "RAK_4631" +$ sh build.sh build-matching-firmwares + +Build all companion firmwares +$ sh build.sh build-companion-firmwares + +Build all repeater firmwares +$ sh build.sh build-repeater-firmwares + +Build all chat room server firmwares +$ sh build.sh build-room-server-firmwares +EOF +} + +# Catch cries for help before doing anything else. +case $1 in + help|usage|-h|--help) + global_usage + exit 1 + ;; +esac + # get a list of pio env names that start with "env:" get_pio_envs() { From 3210475f352c54e795310e908bd0c07a9b2ffa97 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 18 Oct 2025 12:33:43 +0200 Subject: [PATCH 033/409] CommonCli: Remove dependency on target.h --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 2 +- src/helpers/CommonCLI.cpp | 26 +++++++++++++------------- src/helpers/CommonCLI.h | 7 ++++--- variants/t1000-e/platformio.ini | 1 + 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4bb9a2bbe2..374384bd63 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -586,7 +586,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index c1a39a11fc..a9ba799811 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -580,7 +580,7 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) { MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; _logging = false; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 92ea188930..8f8a11fed3 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -651,7 +651,7 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 87f20f5ad6..b8bb698a5c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -166,7 +166,7 @@ uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) { AdvertDataBuilder builder(node_type, _prefs->node_name); return builder.encodeTo(app_data); } else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(node_type, _prefs->node_name, sensors.node_lat, sensors.node_lon); + AdvertDataBuilder builder(node_type, _prefs->node_name, _sensors->node_lat, _sensors->node_lon); return builder.encodeTo(app_data); } else { AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon); @@ -533,7 +533,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s", _board->getManufacturerName()); } else if (memcmp(command, "sensor get ", 11) == 0) { const char* key = command + 11; - const char* val = sensors.getSettingByKey(key); + const char* val = _sensors->getSettingByKey(key); if (val != NULL) { sprintf(reply, "> %s", val); } else { @@ -545,7 +545,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' '); const char *key = (num > 0) ? parts[0] : ""; const char *value = (num > 1) ? parts[1] : "null"; - if (sensors.setSettingByKey(key, value)) { + if (_sensors->setSettingByKey(key, value)) { strcpy(reply, "ok"); } else { strcpy(reply, "can't find custom var"); @@ -553,7 +553,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(command, "sensor list", 11) == 0) { char* dp = reply; int start = 0; - int end = sensors.getNumSettings(); + int end = _sensors->getNumSettings(); if (strlen(command) > 11) { start = _atoi(command+12); } @@ -565,8 +565,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch int i; for (i = start; i < end && (dp-reply < 134); i++) { sprintf(dp, "%s=%s\n", - sensors.getSettingName(i), - sensors.getSettingValue(i)); + _sensors->getSettingName(i), + _sensors->getSettingValue(i)); dp = strchr(dp, 0); } if (i < end) { @@ -577,7 +577,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { - if (sensors.setSettingByKey("gps", "1")) { + if (_sensors->setSettingByKey("gps", "1")) { _prefs->gps_enabled = 1; savePrefs(); strcpy(reply, "ok"); @@ -585,7 +585,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "gps toggle not found"); } } else if (memcmp(command, "gps off", 7) == 0) { - if (sensors.setSettingByKey("gps", "0")) { + if (_sensors->setSettingByKey("gps", "0")) { _prefs->gps_enabled = 0; savePrefs(); strcpy(reply, "ok"); @@ -593,13 +593,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "gps toggle not found"); } } else if (memcmp(command, "gps sync", 8) == 0) { - LocationProvider * l = sensors.getLocationProvider(); + LocationProvider * l = _sensors->getLocationProvider(); if (l != NULL) { l->syncTime(); } } else if (memcmp(command, "gps setloc", 10) == 0) { - _prefs->node_lat = sensors.node_lat; - _prefs->node_lon = sensors.node_lon; + _prefs->node_lat = _sensors->node_lat; + _prefs->node_lon = _sensors->node_lon; savePrefs(); strcpy(reply, "ok"); } else if (memcmp(command, "gps advert", 10) == 0) { @@ -633,12 +633,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "error"); } } else if (memcmp(command, "gps", 3) == 0) { - LocationProvider * l = sensors.getLocationProvider(); + LocationProvider * l = _sensors->getLocationProvider(); if (l != NULL) { bool enabled = l->isEnabled(); // is EN pin on ? bool fix = l->isValid(); // has fix ? int sats = l->satellitesCount(); - bool active = !strcmp(sensors.getSettingByKey("gps"), "1"); + bool active = !strcmp(_sensors->getSettingByKey("gps"), "1"); if (enabled) { sprintf(reply, "on, %s, %s, %d sats", active?"active":"deactivated", diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ce56701648..ea59aa92dd 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -2,7 +2,7 @@ #include "Mesh.h" #include -#include +#include #if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) #define WITH_BRIDGE @@ -85,6 +85,7 @@ class CommonCLI { NodePrefs* _prefs; CommonCLICallbacks* _callbacks; mesh::MainBoard* _board; + SensorManager* _sensors; char tmp[PRV_KEY_SIZE*2 + 4]; mesh::RTCClock* getRTCClock() { return _rtc; } @@ -92,8 +93,8 @@ class CommonCLI { void loadPrefsInt(FILESYSTEM* _fs, const char* filename); public: - CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks) - : _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { } + CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, NodePrefs* prefs, CommonCLICallbacks* callbacks) + : _board(&board), _rtc(&rtc), _sensors(&sensors), _prefs(prefs), _callbacks(callbacks) { } void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 6bb3bdb514..555b182fb4 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -26,6 +26,7 @@ build_flags = ${nrf52_base.build_flags} -D P_LORA_RESET=42 ; P1.10 -D LR11X0_DIO_AS_RF_SWITCH=true -D LR11X0_DIO3_TCXO_VOLTAGE=1.6 + -D ENV_INCLUDE_GPS=1 build_src_filter = ${nrf52_base.build_src_filter} + + From f085a9d6c560d598c6692b9e123cb9df50e4b123 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 18 Oct 2025 13:10:17 +0200 Subject: [PATCH 034/409] tracker_l1_eink: set UI_HAS_JOYSTICK --- variants/wio-tracker-l1-eink/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index a41e9e23fa..deb85f5e77 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -49,6 +49,7 @@ build_flags = ${WioTrackerL1Eink.build_flags} -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=GxEPDDisplay + -D UI_HAS_JOYSTICK=1 -D PIN_BUZZER=12 -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 From 7d62a2783652c096c9038edd985ff92431091149 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 18 Oct 2025 13:37:18 +0200 Subject: [PATCH 035/409] uitask: bring back buzzer toggle on tracker l1 --- examples/companion_radio/ui-new/UITask.cpp | 4 ++++ variants/wio-tracker-l1/target.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index b6484a0011..7c75a08928 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -683,6 +683,10 @@ void UITask::loop() { } else if (ev == BUTTON_EVENT_LONG_PRESS) { c = handleLongPress(KEY_RIGHT); } + ev = back_btn.check(); + if (ev == BUTTON_EVENT_TRIPLE_CLICK) { + c = handleTripleClick(KEY_SELECT); + } #elif defined(PIN_USER_BTN) int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index e7b73040bb..64866de004 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -24,7 +24,7 @@ EnvironmentSensorManager sensors = EnvironmentSensorManager(); MomentaryButton user_btn(PIN_USER_BTN, 1000, true, false, false); MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true, false, false); MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true, false, false); - MomentaryButton back_btn(PIN_BACK_BTN, 1000, true, false, false); + MomentaryButton back_btn(PIN_BACK_BTN, 1000, true, false, true); #endif bool radio_init() { From ce707923099b8739ac3cb88365797b262605794a Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 18 Oct 2025 14:03:27 +0200 Subject: [PATCH 036/409] lgfx_display: better handle display class construction --- src/helpers/ui/LGFXDisplay.h | 11 +++-------- .../sensecap_indicator-espnow/SCIndicatorDisplay.h | 3 +-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/helpers/ui/LGFXDisplay.h b/src/helpers/ui/LGFXDisplay.h index 81d0239f28..ad7212ecf6 100644 --- a/src/helpers/ui/LGFXDisplay.h +++ b/src/helpers/ui/LGFXDisplay.h @@ -1,9 +1,3 @@ - -/* - * Base class for LovyanGFX supported display (works on ESP32 mainly) - * You can extend this class to support your display, providing your own LGFX - */ - #pragma once #include @@ -20,11 +14,12 @@ class LGFXDisplay : public DisplayDriver { LGFX_Device* display; LGFX_Sprite buffer; - bool _isOn; + bool _isOn = false; int _color = TFT_WHITE; public: - LGFXDisplay(int w, int h):DisplayDriver(w/UI_ZOOM, h/UI_ZOOM) {_isOn = false;} + LGFXDisplay(int w, int h, LGFX_Device &disp) + : DisplayDriver(w/UI_ZOOM, h/UI_ZOOM), display(&disp) {} bool begin(); bool isOn() override { return _isOn; } void turnOn() override; diff --git a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h index 6a7e31771d..aabedd2401 100644 --- a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h +++ b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h @@ -124,6 +124,5 @@ class LGFX : public lgfx::LGFX_Device class SCIndicatorDisplay : public LGFXDisplay { LGFX disp; public: - SCIndicatorDisplay() : LGFXDisplay(480, 480) - { display=&disp; } + SCIndicatorDisplay() : LGFXDisplay(480, 480, disp) {} }; From 37dc715a8e5fbec0f0e2cb9537ad418922c9a8b5 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 18 Oct 2025 23:37:58 +0200 Subject: [PATCH 037/409] SensorManager: remove setSettingByKey --- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/CommonCLI.cpp | 6 +++--- src/helpers/SensorManager.h | 10 ---------- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index c45c141dcd..729c5b7d3b 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -137,7 +137,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 60ef1e735e..f6ce31e513 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -151,7 +151,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index cdc3940c30..ff0698dc90 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -151,7 +151,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif }; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 87f20f5ad6..77f0b0856f 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -545,7 +545,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' '); const char *key = (num > 0) ? parts[0] : ""; const char *value = (num > 1) ? parts[1] : "null"; - if (sensors.setSettingByKey(key, value)) { + if (sensors.setSettingValue(key, value)) { strcpy(reply, "ok"); } else { strcpy(reply, "can't find custom var"); @@ -577,7 +577,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { - if (sensors.setSettingByKey("gps", "1")) { + if (sensors.setSettingValue("gps", "1")) { _prefs->gps_enabled = 1; savePrefs(); strcpy(reply, "ok"); @@ -585,7 +585,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "gps toggle not found"); } } else if (memcmp(command, "gps off", 7) == 0) { - if (sensors.setSettingByKey("gps", "0")) { + if (sensors.setSettingValue("gps", "0")) { _prefs->gps_enabled = 0; savePrefs(); strcpy(reply, "ok"); diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 38c1d806b7..89a174c228 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -34,14 +34,4 @@ class SensorManager { } return NULL; } - - bool setSettingByKey(const char* key, const char* value) { - int num = getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(getSettingName(i), key) == 0) { - return setSettingValue(key, value); - } - } - return false; - } }; From a421215e848d7ce6e2afc9559bdeeae3fd7a2435 Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 18 Oct 2025 23:42:28 +0200 Subject: [PATCH 038/409] all nrf52 devices: force framework-arduinoadafruitnrf52 version to 1.10700.0 --- platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platformio.ini b/platformio.ini index d4600d00cc..1200f5998a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -76,6 +76,8 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ [nrf52_base] extends = arduino_base platform = nordicnrf52 +platform_packages = + framework-arduinoadafruitnrf52 @ 1.10700.0 extra_scripts = create-uf2.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM From a5070077ba058e3dfff7e1668a0a355d00cf07e8 Mon Sep 17 00:00:00 2001 From: recrof Date: Sun, 19 Oct 2025 00:02:38 +0200 Subject: [PATCH 039/409] equalize RAK with all other nrf52 variants and use newer platform with all important fixes --- boards/rak4631.json | 72 ++++++++++ variants/rak4631/platformio.ini | 3 +- variants/rak4631/variant.cpp | 49 +++++++ variants/rak4631/variant.h | 172 ++++++++++++++++++++++++ variants/rak_wismesh_tag/platformio.ini | 3 +- 5 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 boards/rak4631.json create mode 100644 variants/rak4631/variant.cpp create mode 100644 variants/rak4631/variant.h diff --git a/boards/rak4631.json b/boards/rak4631.json new file mode 100644 index 0000000000..8d820fce6e --- /dev/null +++ b/boards/rak4631.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "WisCore RAK4631 Board", + "mcu": "nrf52840", + "variant": "WisCore_RAK4631_Board", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": [ + "arduino" + ], + "name": "WisCore RAK4631 Board", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.rakwireless.com", + "vendor": "RAKwireless" +} diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index a8807e36af..9a0504dcc4 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -1,7 +1,6 @@ [rak4631] extends = nrf52_base -platform = https://github.com/maxgerhardt/platform-nordicnrf52.git#rak -board = wiscore_rak4631 +board = rak4631 board_check = true build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} diff --git a/variants/rak4631/variant.cpp b/variants/rak4631/variant.cpp new file mode 100644 index 0000000000..bd85e97133 --- /dev/null +++ b/variants/rak4631/variant.cpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // P0 + 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , + 8 , 9 , 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2);; +} + diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h new file mode 100644 index 0000000000..e83d1339ed --- /dev/null +++ b/variants/rak4631/variant.h @@ -0,0 +1,172 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_RAK4630_ +#define _VARIANT_RAK4630_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + + /* + * WisBlock Base GPIO definitions + */ + static const uint8_t WB_IO1 = 17; // SLOT_A SLOT_B + static const uint8_t WB_IO2 = 34; // SLOT_A SLOT_B + static const uint8_t WB_IO3 = 21; // SLOT_C + static const uint8_t WB_IO4 = 4; // SLOT_C + static const uint8_t WB_IO5 = 9; // SLOT_D + static const uint8_t WB_IO6 = 10; // SLOT_D + static const uint8_t WB_SW1 = 33; // IO_SLOT + static const uint8_t WB_A0 = 5; // IO_SLOT + static const uint8_t WB_A1 = 31; // IO_SLOT + static const uint8_t WB_I2C1_SDA = 13; // SENSOR_SLOT IO_SLOT + static const uint8_t WB_I2C1_SCL = 14; // SENSOR_SLOT IO_SLOT + static const uint8_t WB_I2C2_SDA = 24; // IO_SLOT + static const uint8_t WB_I2C2_SCL = 25; // IO_SLOT + static const uint8_t WB_SPI_CS = 26; // IO_SLOT + static const uint8_t WB_SPI_CLK = 3; // IO_SLOT + static const uint8_t WB_SPI_MISO = 29; // IO_SLOT + static const uint8_t WB_SPI_MOSI = 30; // IO_SLOT + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ +// RAK4631 has no buttons + +/* + * Analog pins + */ +#define PIN_A0 (5) //(3) +#define PIN_A1 (31) //(4) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + + static const uint8_t A0 = PIN_A0; + static const uint8_t A1 = PIN_A1; + static const uint8_t A2 = PIN_A2; + static const uint8_t A3 = PIN_A3; + static const uint8_t A4 = PIN_A4; + static const uint8_t A5 = PIN_A5; + static const uint8_t A6 = PIN_A6; + static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + + static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +// TXD1 RXD1 on Base Board +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// TXD0 RXD0 on Base Board +#define PIN_SERIAL2_RX (19) +#define PIN_SERIAL2_TX (20) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (29) +#define PIN_SPI_MOSI (30) +#define PIN_SPI_SCK (3) + + static const uint8_t SS = 26; + static const uint8_t MOSI = PIN_SPI_MOSI; + static const uint8_t MISO = PIN_SPI_MISO; + static const uint8_t SCK = PIN_SPI_SCK; + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 2 + +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) + +#define PIN_WIRE1_SDA (24) +#define PIN_WIRE1_SCL (25) + + // QSPI Pins + // QSPI occupied by GPIO's + #define PIN_QSPI_SCK 3 // 19 + #define PIN_QSPI_CS 26 // 17 + #define PIN_QSPI_IO0 30 // 20 + #define PIN_QSPI_IO1 29 // 21 + #define PIN_QSPI_IO2 28 // 22 + #define PIN_QSPI_IO3 2 // 23 + + // On-board QSPI Flash + // No onboard flash + #define EXTERNAL_FLASH_DEVICES IS25LP080D + #define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index a55cec192c..37593f6162 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -1,7 +1,6 @@ [rak_wismesh_tag] extends = nrf52_base -platform = https://github.com/maxgerhardt/platform-nordicnrf52.git#rak -board = wiscore_rak4631 +board = rak4631 board_check = true build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} From 31b8f7252a6078782818cd39a657ba740dcba130 Mon Sep 17 00:00:00 2001 From: Tomas P Date: Sun, 19 Oct 2025 20:44:27 +0200 Subject: [PATCH 040/409] Support for Elecrow Thinknode M2 --- boards/ESP32-S3-WROOM-1-N4.json | 39 +++++ variants/thinknode_m2/ThinknodeM2Board.cpp | 32 ++++ variants/thinknode_m2/ThinknodeM2Board.h | 18 +++ variants/thinknode_m2/pins_arduino.h | 28 ++++ variants/thinknode_m2/platformio.ini | 171 +++++++++++++++++++++ variants/thinknode_m2/target.cpp | 57 +++++++ variants/thinknode_m2/target.h | 32 ++++ variants/thinknode_m2/variant.h | 15 ++ 8 files changed, 392 insertions(+) create mode 100644 boards/ESP32-S3-WROOM-1-N4.json create mode 100644 variants/thinknode_m2/ThinknodeM2Board.cpp create mode 100644 variants/thinknode_m2/ThinknodeM2Board.h create mode 100644 variants/thinknode_m2/pins_arduino.h create mode 100644 variants/thinknode_m2/platformio.ini create mode 100644 variants/thinknode_m2/target.cpp create mode 100644 variants/thinknode_m2/target.h create mode 100644 variants/thinknode_m2/variant.h diff --git a/boards/ESP32-S3-WROOM-1-N4.json b/boards/ESP32-S3-WROOM-1-N4.json new file mode 100644 index 0000000000..160926b21e --- /dev/null +++ b/boards/ESP32-S3-WROOM-1-N4.json @@ -0,0 +1,39 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "-D ARDUINO_USB_CDC_ON_BOOT=0", + "-D ARDUINO_USB_MSC_ON_BOOT=0", + "-D ARDUINO_USB_DFU_ON_BOOT=0", + "-D ARDUINO_USB_MODE=0", + "-D ARDUINO_RUNNING_CORE=1", + "-D ARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "ESP32-S3-WROOM-1-N4" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "ESP32-S3-WROOM-1-N4 (4 MB Flash, No PSRAM)", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 524288, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf", + "vendor": "Espressif" +} diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp new file mode 100644 index 0000000000..3a2f067e28 --- /dev/null +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -0,0 +1,32 @@ +#include "ThinknodeM2Board.h" + + + +void ThinknodeM2Board::begin() { + ESP32Board::begin(); + pinMode(PIN_VEXT_EN, OUTPUT); // init display + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // pin needs to be high + delay(10); + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // need to do this twice. do not know why.. + pinMode(PIN_STATUS_LED, OUTPUT); // init power led + } + + void ThinknodeM2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_deep_sleep_start(); + } + + void ThinknodeM2Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t ThinknodeM2Board::getBattMilliVolts() { + analogReadResolution(12); + delay(10); + float volts = (analogRead(PIN_VBAT_READ) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096; + analogReadResolution(10); + return volts * 1000; + } + + const char* ThinknodeM2Board::getManufacturerName() const { + return "Elecrow ThinkNode M2"; + } \ No newline at end of file diff --git a/variants/thinknode_m2/ThinknodeM2Board.h b/variants/thinknode_m2/ThinknodeM2Board.h new file mode 100644 index 0000000000..8011fae67d --- /dev/null +++ b/variants/thinknode_m2/ThinknodeM2Board.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include + +class ThinknodeM2Board : public ESP32Board { + +public: + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; \ No newline at end of file diff --git a/variants/thinknode_m2/pins_arduino.h b/variants/thinknode_m2/pins_arduino.h new file mode 100644 index 0000000000..a5dee3635d --- /dev/null +++ b/variants/thinknode_m2/pins_arduino.h @@ -0,0 +1,28 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial +static const uint8_t TX = GPS_TX; +static const uint8_t RX = GPS_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = P_LORA_NSS; +static const uint8_t SCK = P_LORA_SCLK; +static const uint8_t MOSI = P_LORA_MISO; +static const uint8_t MISO = P_LORA_MOSI; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = PIN_BOARD_SCL; +static const uint8_t SDA = PIN_BOARD_SDA; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini new file mode 100644 index 0000000000..0238947f96 --- /dev/null +++ b/variants/thinknode_m2/platformio.ini @@ -0,0 +1,171 @@ +[ThinkNode_M2] +extends = esp32_base +board = ESP32-S3-WROOM-1-N4 +build_flags = ${esp32_base.build_flags} + -I variants/thinknode_m2 + -D THINKNODE_M2 + -D GPS_RX=44 + -D GPS_TX=43 + -D PIN_VEXT_EN=46 + -D PIN_BUZZER=5 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_BOARD_SCL=15 + -D PIN_BOARD_SDA=16 + -D P_LORA_DIO_1=3 + -D P_LORA_NSS=10 + -D P_LORA_RESET=21 ; RADIOLIB_NC + -D P_LORA_BUSY=14 ; DIO2 = 38 + -D P_LORA_SCLK=12 + -D P_LORA_MISO=13 + -D P_LORA_MOSI=11 + -D PIN_USER_BTN=47 + -D PIN_STATUS_LED=6 + -D PIN_LED=6 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D DISPLAY_CLASS=SH1106Display + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 + -D MESH_DEBUG=1 +build_src_filter = ${esp32_base.build_src_filter} + + + + + + + +<../variants/thinknode_m2> +lib_deps = ${esp32_base.lib_deps} + adafruit/Adafruit SH110X @ ~2.1.13 + adafruit/Adafruit GFX Library @ ^1.12.1 + +[env:ThinkNode_M2_Repeater] +extends = ThinkNode_M2 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M2.build_flags} + -D ADVERT_NAME='"Thinknode M2 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M2.lib_deps} + ${esp32_ota.lib_deps} + +; [env:ThinkNode_M2_Repeater_bridge_rs232] +; extends = ThinkNode_M2 +; build_src_filter = ${ThinkNode_M2.build_src_filter} +; + +; +<../examples/simple_repeater/*.cpp> +; build_flags = +; ${ThinkNode_M2.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${ThinkNode_M2.lib_deps} +; ${esp32_ota.lib_deps} + +[env:ThinkNode_M2_Repeater_bridge_espnow] +extends = ThinkNode_M2 +build_src_filter = ${ThinkNode_M2.build_src_filter} + + + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M2.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M2.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M2_room_server] +extends = ThinkNode_M2 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${ThinkNode_M2.build_flags} + -D ADVERT_NAME='"Thinknode M2 Room Server"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M2.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M2_terminal_chat] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M2_companion_radio_ble] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M2.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M2_companion_radio_serial] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D SERIAL_TX=D6 + -D SERIAL_RX=D7 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M2.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m2/target.cpp b/variants/thinknode_m2/target.cpp new file mode 100644 index 0000000000..cb3c1624d0 --- /dev/null +++ b/variants/thinknode_m2/target.cpp @@ -0,0 +1,57 @@ +#include +#include "target.h" + +ThinknodeM2Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + pinMode(21, INPUT); + pinMode(48, OUTPUT); + #if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/thinknode_m2/target.h b/variants/thinknode_m2/target.h new file mode 100644 index 0000000000..b05def8aa4 --- /dev/null +++ b/variants/thinknode_m2/target.h @@ -0,0 +1,32 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +//#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ThinknodeM2Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + + \ No newline at end of file diff --git a/variants/thinknode_m2/variant.h b/variants/thinknode_m2/variant.h new file mode 100644 index 0000000000..928f56ec03 --- /dev/null +++ b/variants/thinknode_m2/variant.h @@ -0,0 +1,15 @@ +#define I2C_SCL 15 +#define I2C_SDA 16 +#define PIN_VBAT_READ 17 +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (1.548F) +#define PIN_BUZZER 5 +#define PIN_VEXT_EN_ACTIVE HIGH +#define PIN_VEXT_EN 46 +#define PIN_USER_BTN 47 +#define PIN_LED 6 +#define PIN_STATUS_LED 6 +#define PIN_PWRBTN 4 + + + From 5d495d505a1db06254254f5625a18e96647e46c3 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 21 Oct 2025 00:34:57 +1100 Subject: [PATCH 041/409] Revert Heltec T114 power savings As discussed on discord with @recrof people are having issues, possibly due to these changes. See https://github.com/meshcore-dev/MeshCore/issues/746 This reverts commit a16e011bd21cf3070f04b3513d562add0d090838. --- variants/heltec_t114/T114Board.cpp | 39 ------------------------------ variants/heltec_t114/T114Board.h | 5 ---- 2 files changed, 44 deletions(-) diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index 3b40e7cf5d..f8d170b5df 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -24,45 +24,6 @@ void T114Board::begin() { pinMode(PIN_VBAT_READ, INPUT); - // Enable SoftDevice low-power mode - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - - // Enable DC/DC converter for better efficiency (REG1 stage) - NRF_POWER->DCDCEN = 1; - - // Power down unused communication peripherals - // UART1 - Not used on T114 - NRF_UARTE1->ENABLE = 0; - - // SPIM2/SPIS2 - Not used (SPI is on SPIM0) - NRF_SPIM2->ENABLE = 0; - NRF_SPIS2->ENABLE = 0; - - // TWI1 (I2C1) - Not used (I2C is on TWI0) - NRF_TWIM1->ENABLE = 0; - NRF_TWIS1->ENABLE = 0; - - // PWM modules - Not used for standard T114 functions - NRF_PWM1->ENABLE = 0; - NRF_PWM2->ENABLE = 0; - NRF_PWM3->ENABLE = 0; - - // PDM (Digital Microphone Interface) - Not used - NRF_PDM->ENABLE = 0; - - // I2S - Not used - NRF_I2S->ENABLE = 0; - - // QSPI - Not used (no external flash) - NRF_QSPI->ENABLE = 0; - - // Disable unused analog peripherals - // SAADC channels - only keep what's needed for battery monitoring - NRF_SAADC->ENABLE = 0; // Re-enable only when needed for measurements - - // COMP - Comparator not used - NRF_COMP->ENABLE = 0; - #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); #endif diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 7a7a654b5d..49d1ec3731 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -27,9 +27,6 @@ class T114Board : public mesh::MainBoard { uint16_t getBattMilliVolts() override { int adcvalue = 0; - - NRF_SAADC->ENABLE = 1; - analogReadResolution(12); analogReference(AR_INTERNAL_3_0); pinMode(PIN_BAT_CTL, OUTPUT); // battery adc can be read only ctrl pin 6 set to high @@ -39,8 +36,6 @@ class T114Board : public mesh::MainBoard { adcvalue = analogRead(PIN_VBAT_READ); digitalWrite(6, 0); - NRF_SAADC->ENABLE = 0; - return (uint16_t)((float)adcvalue * MV_LSB * 4.9); } From ec05d40b3c948aec9e01ac0560204d3388f5336b Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Mon, 20 Oct 2025 18:49:34 +0200 Subject: [PATCH 042/409] Add Seeed Wio WM1110 Dev Board variant --- variants/wio_wm1110/WioWM1110Board.cpp | 75 +++++++++++++ variants/wio_wm1110/WioWM1110Board.h | 58 ++++++++++ variants/wio_wm1110/platformio.ini | 85 +++++++++++++++ variants/wio_wm1110/target.cpp | 110 +++++++++++++++++++ variants/wio_wm1110/target.h | 21 ++++ variants/wio_wm1110/variant.cpp | 92 ++++++++++++++++ variants/wio_wm1110/variant.h | 145 +++++++++++++++++++++++++ 7 files changed, 586 insertions(+) create mode 100644 variants/wio_wm1110/WioWM1110Board.cpp create mode 100644 variants/wio_wm1110/WioWM1110Board.h create mode 100644 variants/wio_wm1110/platformio.ini create mode 100644 variants/wio_wm1110/target.cpp create mode 100644 variants/wio_wm1110/target.h create mode 100644 variants/wio_wm1110/variant.cpp create mode 100644 variants/wio_wm1110/variant.h diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp new file mode 100644 index 0000000000..ca3638b344 --- /dev/null +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -0,0 +1,75 @@ +#ifdef WIO_WM1110 + +#include +#include +#include + +#include "WioWM1110Board.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 WioWM1110Board::begin() { + startup_reason = BD_STARTUP_NORMAL; + + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + NRF_POWER->DCDCEN = 1; + + pinMode(BATTERY_PIN, INPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_RED, OUTPUT); + pinMode(SENSOR_POWER_PIN, OUTPUT); + + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_RED, LOW); + digitalWrite(SENSOR_POWER_PIN, LOW); + + Serial1.begin(115200); + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + + delay(10); +} + +bool WioWM1110Board::startOTAUpdate(const char *id, char reply[]) { + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + Bluefruit.setTxPower(4); + Bluefruit.setName("WM1110_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + bledfu.begin(); + + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); + Bluefruit.Advertising.setFastTimeout(30); + Bluefruit.Advertising.start(0); + + strcpy(reply, "OK - started"); + return true; +} + +#endif + diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h new file mode 100644 index 0000000000..823acbc7df --- /dev/null +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#ifdef WIO_WM1110 + +#ifdef Serial + #undef Serial +#endif +#define Serial Serial1 + +class WioWM1110Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(LED_GREEN) + void onBeforeTransmit() override { + digitalWrite(LED_RED, HIGH); + } + void onAfterTransmit() override { + digitalWrite(LED_RED, LOW); + } +#endif + + uint16_t getBattMilliVolts() override { + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(BATTERY_PIN); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE * 1000.0) / 4096.0; + } + + const char* getManufacturerName() const override { + return "Seeed Wio WM1110"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; + + void enableSensorPower(bool enable) { + digitalWrite(SENSOR_POWER_PIN, enable ? HIGH : LOW); + if (enable) { + delay(100); + } + } +}; + +#endif + diff --git a/variants/wio_wm1110/platformio.ini b/variants/wio_wm1110/platformio.ini new file mode 100644 index 0000000000..313430ff72 --- /dev/null +++ b/variants/wio_wm1110/platformio.ini @@ -0,0 +1,85 @@ +[wio_wm1110] +extends = nrf52_base +board = seeed-xiao-afruitnrf52-nrf52840 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 + -I variants/wio_wm1110 + -D NRF52_PLATFORM + -D WIO_WM1110 +; -D MESH_DEBUG=1 + -D RADIO_CLASS=CustomLR1110 + -D WRAPPER_CLASS=CustomLR1110Wrapper + -D LORA_TX_POWER=22 + -D RX_BOOSTED_GAIN=true + -D P_LORA_DIO_1=40 + -D P_LORA_RESET=42 + -D P_LORA_BUSY=43 + -D P_LORA_NSS=44 + -D P_LORA_SCLK=45 + -D P_LORA_MOSI=46 + -D P_LORA_MISO=47 + -D LR11X0_DIO_AS_RF_SWITCH=true + -D LR11X0_DIO3_TCXO_VOLTAGE=1.8 + -D RF_SWITCH_TABLE +build_src_filter = ${nrf52_base.build_src_filter} + + + + + +<../variants/wio_wm1110> +debug_tool = jlink +upload_protocol = jlink +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit LIS3DH @ ^1.2.4 + adafruit/Adafruit SHT4x Library @ ^1.0.4 + +[env:wio_wm1110_repeater] +extends = wio_wm1110 +build_flags = + ${wio_wm1110.build_flags} + -D ADVERT_NAME='"WM1110 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 = ${wio_wm1110.build_src_filter} + +<../examples/simple_repeater/*.cpp> + +[env:wio_wm1110_room_server] +extends = wio_wm1110 +build_flags = + ${wio_wm1110.build_flags} + -D ADVERT_NAME='"WM1110 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${wio_wm1110.build_src_filter} + +<../examples/simple_room_server/*.cpp> + +[env:wio_wm1110_companion_radio_ble] +extends = wio_wm1110 +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = + ${wio_wm1110.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D QSPIFLASH=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${wio_wm1110.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${wio_wm1110.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/wio_wm1110/target.cpp b/variants/wio_wm1110/target.cpp new file mode 100644 index 0000000000..cc302a852f --- /dev/null +++ b/variants/wio_wm1110/target.cpp @@ -0,0 +1,110 @@ +#include +#include "target.h" +#include +#include + +class WM1110LocationProvider : public LocationProvider { +public: + long getLatitude() override { return 0; } + long getLongitude() override { return 0; } + long getAltitude() override { return 0; } + long satellitesCount() override { return 0; } + bool isValid() override { return false; } + long getTimestamp() override { return 0; } + void sendSentence(const char* sentence) override {} + void reset() override {} + void begin() override {} + void stop() override {} + void loop() override {} + bool isEnabled() override { return false; } +}; + +WioWM1110Board 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 rtc_clock; +WM1110LocationProvider location_provider; +EnvironmentSensorManager sensors(location_provider); + +#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_LR11X0_DIO7, + RADIOLIB_LR11X0_DIO8, + RADIOLIB_NC +}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 DIO7 DIO8 + { LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW }}, + { LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH }}, + { LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH }}, + { LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH }}, + { LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW }}, + { LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW }}, + { LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW }}, + END_OF_MODE_TABLE, +}; +#endif + +bool radio_init() { + board.enableSensorPower(true); + +#ifdef LR11X0_DIO3_TCXO_VOLTAGE + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.8f; +#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 +} + diff --git a/variants/wio_wm1110/target.h b/variants/wio_wm1110/target.h new file mode 100644 index 0000000000..9bd4a22b80 --- /dev/null +++ b/variants/wio_wm1110/target.h @@ -0,0 +1,21 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include "WioWM1110Board.h" +#include +#include +#include + +extern WioWM1110Board board; +extern WRAPPER_CLASS radio_driver; +extern VolatileRTCClock 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/wio_wm1110/variant.cpp b/variants/wio_wm1110/variant.cpp new file mode 100644 index 0000000000..9691a304a5 --- /dev/null +++ b/variants/wio_wm1110/variant.cpp @@ -0,0 +1,92 @@ +/* + * variant.cpp - Seeed Wio WM1110 Dev Board + * Pin mapping for nRF52840 + */ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[PINS_COUNT + 1] = +{ + 0, // P0.00 + 1, // P0.01 + 2, // P0.02, AIN0, SENSOR_AIN_0 + 3, // P0.03, AIN1, SENSOR_AIN_1 + 4, // P0.04, AIN2, SENSOR_AIN_2 + 5, // P0.05, AIN3, SENSOR_AIN_3 + 6, // P0.06, PIN_SERIAL2_RX, SENSOR_RXD + 7, // P0.07, SENSOR_POWER_PIN + 8, // P0.08, PIN_SERIAL2_TX, SENSOR_TXD + 9, // P0.09 + 10, // P0.10 + 11, // P0.11, LIS3DH_INT_PIN_1, SENSOR_INT_1 + 12, // P0.12, LIS3DH_INT_PIN_2, SENSOR_INT_2 + 13, // P0.13, LED_GREEN, USER_LED_G + 14, // P0.14, LED_RED, USER_LED_R + 15, // P0.15 + 16, // P0.16 + 17, // P0.17 + 18, // P0.18 + 19, // P0.19 + 20, // P0.20 + 21, // P0.21 + 22, // P0.22, PIN_SERIAL1_RX, DEBUG_RX_PIN + 23, // P0.23 + 24, // P0.24, PIN_SERIAL1_TX, DEBUG_TX_PIN + 25, // P0.25 + 26, // P0.26, PIN_WIRE_SCL, SENSOR_SCL + 27, // P0.27, PIN_WIRE_SDA, SENSOR_SDA + 28, // P0.28, AIN4, SENSOR_AIN_4 + 29, // P0.29, AIN5, SENSOR_AIN_5 + 30, // P0.30, AIN6, SENSOR_AIN_6 + 31, // P0.31, AIN7, SENSOR_AIN_7, BATTERY_PIN + 32, // P1.00 + 33, // P1.01 + 34, // P1.02 + 35, // P1.03 + 36, // P1.04 + 37, // P1.05, LR1110_GNSS_ANT_PIN + 38, // P1.06 + 39, // P1.07 + 40, // P1.08, LORA_DIO_1, LR1110_IRQ_PIN + 41, // P1.09 + 42, // P1.10, LORA_RESET, LR1110_NRESET_PIN + 43, // P1.11, LORA_BUSY, LR1110_BUSY_PIN + 44, // P1.12, PIN_SPI_NSS, LR1110_SPI_NSS_PIN + 45, // P1.13, PIN_SPI_SCK, LR1110_SPI_SCK_PIN + 46, // P1.14, PIN_SPI_MOSI, LR1110_SPI_MOSI_PIN + 47, // P1.15, PIN_SPI_MISO, LR1110_SPI_MISO_PIN + 255, // NRFX_SPIM_PIN_NOT_USED +}; + +void initVariant() +{ + // All pins output HIGH by default. + // https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino/blob/fab7d30a997a1dfeef9d1d59bfb549adda73815a/cores/nRF5/wiring.c#L65-L69 + + // Set analog input pins + pinMode(BATTERY_PIN, INPUT); + pinMode(SENSOR_AIN_0, INPUT); + pinMode(SENSOR_AIN_1, INPUT); + pinMode(SENSOR_AIN_2, INPUT); + pinMode(SENSOR_AIN_3, INPUT); + pinMode(SENSOR_AIN_4, INPUT); + pinMode(SENSOR_AIN_5, INPUT); + pinMode(SENSOR_AIN_6, INPUT); + + // Sensor interrupts as inputs + pinMode(LIS3DH_INT_PIN_1, INPUT); + pinMode(LIS3DH_INT_PIN_2, INPUT); + + // Set output pins + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_RED, OUTPUT); + pinMode(SENSOR_POWER_PIN, OUTPUT); + + // Initialize outputs to safe states + digitalWrite(LED_GREEN, HIGH); // Power indicator LED on + digitalWrite(LED_RED, LOW); + digitalWrite(SENSOR_POWER_PIN, LOW); // Sensors powered off initially +} + diff --git a/variants/wio_wm1110/variant.h b/variants/wio_wm1110/variant.h new file mode 100644 index 0000000000..cc72c32887 --- /dev/null +++ b/variants/wio_wm1110/variant.h @@ -0,0 +1,145 @@ +/* + * variant.h - Seeed Wio WM1110 Dev Board + * nRF52840 + LR1110 (LoRa + GNSS + WiFi Scanner) + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define BATTERY_PIN (31) // AIN7 +#define BATTERY_IMMUTABLE +#define ADC_MULTIPLIER (2.0F) + +#define ADC_RESOLUTION (14) +#define BATTERY_SENSE_RES (12) + +#define AREF_VOLTAGE (3.0) + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX (22) +#define PIN_SERIAL1_TX (24) + +#define PIN_SERIAL2_RX (6) +#define PIN_SERIAL2_TX (8) + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define HAS_WIRE (1) +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (27) +#define PIN_WIRE_SCL (26) +#define I2C_NO_RESCAN + +#define SENSOR_POWER_PIN (7) + +#define HAS_LIS3DH (1) +#define LIS3DH_INT_PIN_1 (11) +#define LIS3DH_INT_PIN_2 (12) + +#define HAS_SHT41 (1) + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (47) +#define PIN_SPI_MOSI (46) +#define PIN_SPI_SCK (45) +#define PIN_SPI_NSS (44) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_BUILTIN (13) +#define LED_GREEN (13) +#define LED_RED (14) +#define LED_BLUE LED_RED +#define LED_PIN LED_GREEN + +#define LED_STATE_ON HIGH + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (-1) +#define BUTTON_PIN PIN_BUTTON1 + +//////////////////////////////////////////////////////////////////////////////// +// LR1110 LoRa Radio + GNSS + WiFi + +#define LORA_DIO_1 (40) // P1.8 - LR1110_IRQ_PIN +#define LORA_NSS (PIN_SPI_NSS) // P1.12 +#define LORA_RESET (42) // P1.10 - LR1110_NRESET_PIN +#define LORA_BUSY (43) // P1.11 - LR1110_BUSY_PIN +#define LORA_SCLK (PIN_SPI_SCK) // P1.13 +#define LORA_MISO (PIN_SPI_MISO) // P1.15 +#define LORA_MOSI (PIN_SPI_MOSI) // P1.14 +#define LORA_CS PIN_SPI_NSS // P1.12 + +// LR1110 specific settings +#define LR11X0_DIO_AS_RF_SWITCH true +#define LR11X0_DIO3_TCXO_VOLTAGE 1.8 +#define LR1110_GNSS_ANT_PIN (37) // P1.5 + +// Pin aliases for LR1110 driver compatibility +#define LR1110_IRQ_PIN LORA_DIO_1 +#define LR1110_NRESET_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_BUSY +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCLK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO + +//////////////////////////////////////////////////////////////////////////////// +// Analog Input Pins + +#define SENSOR_AIN_0 (2) +#define SENSOR_AIN_1 (3) +#define SENSOR_AIN_2 (4) +#define SENSOR_AIN_3 (5) +#define SENSOR_AIN_4 (28) +#define SENSOR_AIN_5 (29) +#define SENSOR_AIN_6 (30) +#define SENSOR_AIN_7 (31) + +static const uint8_t A0 = SENSOR_AIN_0; +static const uint8_t A1 = SENSOR_AIN_1; +static const uint8_t A2 = SENSOR_AIN_2; +static const uint8_t A3 = SENSOR_AIN_3; +static const uint8_t A4 = SENSOR_AIN_4; +static const uint8_t A5 = SENSOR_AIN_5; +static const uint8_t A6 = SENSOR_AIN_6; +static const uint8_t A7 = SENSOR_AIN_7; + +//////////////////////////////////////////////////////////////////////////////// +// GPS/GNSS + +#define HAS_GPS 0 +#define PIN_GPS_TX (-1) +#define PIN_GPS_RX (-1) +#define GPS_EN (-1) +#define GPS_RESET (-1) + From 0920dc66637cc9da0abc2228b9241c1abc5c1eb9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:23:45 +0200 Subject: [PATCH 043/409] Fix reversed GPS PINs on G2 and enable timesync --- variants/station_g2/platformio.ini | 4 ++-- variants/station_g2/target.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index bda8b7aea5..bc00bae71e 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -21,8 +21,8 @@ build_flags = -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=38 - -D PIN_GPS_RX=7 - -D PIN_GPS_TX=15 + -D PIN_GPS_RX=15 + -D PIN_GPS_TX=7 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index c2ee97e239..3f0c1404de 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -17,7 +17,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; From 87677fda76dc5c83cbc6feac8f74d041fab2597e Mon Sep 17 00:00:00 2001 From: recrof Date: Wed, 22 Oct 2025 15:15:29 +0200 Subject: [PATCH 044/409] allow spreading factor from 5 and bandwidth from 7.8kHz --- examples/companion_radio/MyMesh.cpp | 4 ++-- src/helpers/CommonCLI.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3772fc1bb1..02f1a21ded 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -706,8 +706,8 @@ void MyMesh::begin(bool has_display) { _prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f); _prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f); _prefs.freq = constrain(_prefs.freq, 400.0f, 2500.0f); - _prefs.bw = constrain(_prefs.bw, 62.5f, 500.0f); - _prefs.sf = constrain(_prefs.sf, 7, 12); + _prefs.bw = constrain(_prefs.bw, 7.8f, 500.0f); + _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b8bb698a5c..32ef632d21 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -77,8 +77,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0, 2.0f); _prefs->airtime_factor = constrain(_prefs->airtime_factor, 0, 9.0f); _prefs->freq = constrain(_prefs->freq, 400.0f, 2500.0f); - _prefs->bw = constrain(_prefs->bw, 62.5f, 500.0f); - _prefs->sf = constrain(_prefs->sf, 7, 12); + _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f); + _prefs->sf = constrain(_prefs->sf, 5, 12); _prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); From a38418e09a450d9564db82b614285406c45c83eb Mon Sep 17 00:00:00 2001 From: Matthias Wientapper Date: Wed, 22 Oct 2025 20:01:15 +0200 Subject: [PATCH 045/409] * Add display of IP address to companion screen --- examples/companion_radio/ui-new/UITask.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a08928..38c09781ec 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2,6 +2,8 @@ #include #include "../MyMesh.h" #include "target.h" +#include + #ifndef AUTO_OFF_MILLIS #define AUTO_OFF_MILLIS 15000 // 15 seconds @@ -129,6 +131,8 @@ class HomeScreen : public UIScreen { bool sensors_scroll = false; int sensors_scroll_offset = 0; int next_sensors_refresh = 0; + + char ipStr[20]; void refresh_sensors() { if (millis() > next_sensors_refresh) { @@ -192,10 +196,17 @@ class HomeScreen : public UIScreen { sprintf(tmp, "MSG: %d", _task->getMsgCount()); display.drawTextCentered(display.width() / 2, 20, tmp); + #ifdef WIFI_SSID + IPAddress ip = WiFi.localIP(); + snprintf(ipStr, sizeof(ipStr), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 54, ipStr); + #endif if (_task->hasConnection()) { display.setColor(DisplayDriver::GREEN); display.setTextSize(1); display.drawTextCentered(display.width() / 2, 43, "< Connected >"); + } else if (the_mesh.getBLEPin() != 0) { // BT pin display.setColor(DisplayDriver::RED); display.setTextSize(2); From ac1513129627e505544264b8ff46282a4a46f3c1 Mon Sep 17 00:00:00 2001 From: Wesley Ellis Date: Wed, 22 Oct 2025 16:17:06 -0400 Subject: [PATCH 046/409] Add support for bmp085/bmp180 temperature/pressure sensor --- platformio.ini | 2 ++ .../sensors/EnvironmentSensorManager.cpp | 29 +++++++++++++++++++ .../sensors/EnvironmentSensorManager.h | 1 + 3 files changed, 32 insertions(+) diff --git a/platformio.ini b/platformio.ini index 1200f5998a..56a5171976 100644 --- a/platformio.ini +++ b/platformio.ini @@ -128,6 +128,7 @@ build_flags = -D ENV_INCLUDE_MLX90614=1 -D ENV_INCLUDE_VL53L0X=1 -D ENV_INCLUDE_BME680=1 + -D ENV_INCLUDE_BMP085=1 lib_deps = adafruit/Adafruit INA3221 Library @ ^1.0.1 adafruit/Adafruit INA219 @ ^1.2.3 @@ -143,3 +144,4 @@ lib_deps = adafruit/Adafruit_VL53L0X @ ^1.2.4 stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit BME680 Library @ ^2.0.4 + adafruit/Adafruit BMP085 Library @ ^1.2.4 diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index bb70c0b5ca..41d50e92c1 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -15,6 +15,12 @@ static Adafruit_BME680 BME680; #endif +#ifdef ENV_INCLUDE_BMP085 +#define TELEM_BMP085_SEALEVELPRESSURE_HPA (1013.25) +#include +static Adafruit_BMP085 BMP085; +#endif + #if ENV_INCLUDE_AHTX0 #define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address #include @@ -305,6 +311,21 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_BMP085 + // first arg is MODE + // 0: ULTRALOWPOWER + // 1: STANDARD + // 2: HIGHRES + // 3: ULTRAHIGHRES + if (BMP085.begin(1, TELEM_WIRE)) { + MESH_DEBUG_PRINTLN("Found sensor BMP085"); + BMP085_initialized = true; + } else { + BMP085_initialized = false; + MESH_DEBUG_PRINTLN("BMP085 was not found at I2C address %02X", 0x77); + } + #endif + return true; } @@ -447,6 +468,14 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_BMP085 + if (BMP085_initialized) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP085.readPressure() / 100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, BMP085.readAltitude(TELEM_BMP085_SEALEVELPRESSURE_HPA * 100)); + } + #endif + } return true; diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 133d26504b..5f1c08e2e7 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -21,6 +21,7 @@ class EnvironmentSensorManager : public SensorManager { bool VL53L0X_initialized = false; bool SHT4X_initialized = false; bool BME680_initialized = false; + bool BMP085_initialized = false; bool gps_detected = false; bool gps_active = false; From 4cfbd3bad556abfdb03d5a00023b28536842b59f Mon Sep 17 00:00:00 2001 From: Wesley Ellis Date: Wed, 22 Oct 2025 16:53:11 -0400 Subject: [PATCH 047/409] Switch BMP085 mode to 0 for ULTRALOWPOWER --- src/helpers/sensors/EnvironmentSensorManager.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 41d50e92c1..98339105bf 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -312,12 +312,9 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_BMP085 - // first arg is MODE - // 0: ULTRALOWPOWER - // 1: STANDARD - // 2: HIGHRES - // 3: ULTRAHIGHRES - if (BMP085.begin(1, TELEM_WIRE)) { + // First argument is MODE (aka oversampling) + // choose ULTRALOWPOWER + if (BMP085.begin(0, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found sensor BMP085"); BMP085_initialized = true; } else { From 8ca3ed28cf49ed4ee4788454044e9e86eb94a0b9 Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:59:43 -0700 Subject: [PATCH 048/409] set PIN_GPS_EN in wismesh tag companion --- variants/rak_wismesh_tag/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 37593f6162..081cb0d077 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -94,6 +94,7 @@ build_flags = -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 + -D PIN_GPS_EN=34 ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${rak_wismesh_tag.build_src_filter} From 2e249e24dcec86e184f0e354a9e3e9b4ce73879b Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Wed, 22 Oct 2025 23:55:51 -0700 Subject: [PATCH 049/409] Updated CayenneLPP to 1.6.1 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 11814a2229..916bb4642a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ lib_deps = rweather/Crypto @ ^0.4.0 adafruit/RTClib @ ^2.1.3 melopero/Melopero RV3028 @ ^1.1.0 - electroniccats/CayenneLPP @ 1.4.0 + electroniccats/CayenneLPP @ 1.6.1 build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 -D LORA_FREQ=869.525 -D LORA_BW=250 From f1824e68b9e64384b941b06fec6ea59596ac09df Mon Sep 17 00:00:00 2001 From: liamcottle Date: Thu, 23 Oct 2025 23:24:40 +1300 Subject: [PATCH 050/409] increase repeater max uptime from 49 days to 136 years --- examples/simple_repeater/MyMesh.cpp | 9 ++++++++- examples/simple_repeater/MyMesh.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 374384bd63..f328c752ea 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -149,7 +149,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t stats.n_packets_recv = radio_driver.getPacketsRecv(); stats.n_packets_sent = radio_driver.getPacketsSent(); stats.total_air_time_secs = getTotalAirTime() / 1000; - stats.total_up_time_secs = _ms->getMillis() / 1000; + stats.total_up_time_secs = uptime_millis / 1000; stats.n_sent_flood = getNumSentFlood(); stats.n_sent_direct = getNumSentDirect(); stats.n_recv_flood = getNumRecvFlood(); @@ -594,6 +594,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc , bridge(&_prefs, _mgr, &rtc) #endif { + last_millis = 0; + uptime_millis = 0; next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; set_radio_at = revert_radio_at = 0; @@ -891,4 +893,9 @@ void MyMesh::loop() { acl.save(_fs); dirty_contacts_expiry = 0; } + + // update uptime + uint32_t now = millis(); + uptime_millis += now - last_millis; + last_millis = now; } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index c45c141dcd..a9ab251ee5 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -78,6 +78,8 @@ struct NeighbourInfo { class MyMesh : public mesh::Mesh, public CommonCLICallbacks { FILESYSTEM* _fs; + uint32_t last_millis; + uint64_t uptime_millis; unsigned long next_local_advert, next_flood_advert; bool _logging; NodePrefs _prefs; From 273a54f104f3462754aeba6e8ace7b3a694b1f5a Mon Sep 17 00:00:00 2001 From: liamcottle Date: Thu, 23 Oct 2025 23:29:08 +1300 Subject: [PATCH 051/409] increase room server max uptime from 49 days to 136 years --- examples/simple_room_server/MyMesh.cpp | 9 ++++++++- examples/simple_room_server/MyMesh.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index a9ba799811..5d245ba164 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -144,7 +144,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t stats.n_packets_recv = radio_driver.getPacketsRecv(); stats.n_packets_sent = radio_driver.getPacketsSent(); stats.total_air_time_secs = getTotalAirTime() / 1000; - stats.total_up_time_secs = _ms->getMillis() / 1000; + stats.total_up_time_secs = uptime_millis / 1000; stats.n_sent_flood = getNumSentFlood(); stats.n_sent_direct = getNumSentDirect(); stats.n_recv_flood = getNumRecvFlood(); @@ -581,6 +581,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { + last_millis = 0; + uptime_millis = 0; next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; _logging = false; @@ -858,4 +860,9 @@ void MyMesh::loop() { } // TODO: periodically check for OLD/inactive entries in known_clients[], and evict + + // update uptime + uint32_t now = millis(); + uptime_millis += now - last_millis; + last_millis = now; } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 60ef1e735e..88e30bf27b 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -88,6 +88,8 @@ struct PostInfo { class MyMesh : public mesh::Mesh, public CommonCLICallbacks { FILESYSTEM* _fs; + uint32_t last_millis; + uint64_t uptime_millis; unsigned long next_local_advert, next_flood_advert; bool _logging; NodePrefs _prefs; From dfb4497c7aab34d2fac2e0964a2b37f2a0cd6958 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 23 Oct 2025 21:44:52 +1100 Subject: [PATCH 052/409] * T114: enabled GPS page in UITask --- variants/heltec_t114/platformio.ini | 1 + variants/heltec_t114/target.cpp | 3 +-- variants/heltec_t114/target.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 53fd5e028d..c482a30ad6 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -170,6 +170,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 + -D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI ; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index d2fa6c4c45..5b786437db 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -74,11 +74,10 @@ bool T114SensorManager::begin() { if (gps_detected) { MESH_DEBUG_PRINTLN("GPS detected"); - digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed } else { MESH_DEBUG_PRINTLN("No GPS detected"); - digitalWrite(GPS_EN, LOW); } + digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed return true; } diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 1876aadcf6..6306cd699c 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -30,6 +30,7 @@ class T114SensorManager : public SensorManager { 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; From 2981fc70e10102b2b9eabbcd3b9ee7c2d608362f Mon Sep 17 00:00:00 2001 From: Woodie-07 Date: Fri, 24 Oct 2025 20:12:02 +0100 Subject: [PATCH 053/409] new workaround --- src/helpers/radiolib/CustomLR1110.h | 69 +++-------------------------- 1 file changed, 7 insertions(+), 62 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index f723bb7f0e..cdf3cc7cf5 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -10,74 +10,19 @@ class CustomLR1110 : public LR1110 { public: CustomLR1110(Module *mod) : LR1110(mod) { } - uint8_t shiftCount = 0; - - int16_t standby() override { - // tx resets the shift, standby is called on tx completion - // this might not actually be what resets it, but it seems to work - // more investigation needed - this->shiftCount = 0; - return LR1110::standby(); - } - size_t getPacketLength(bool update) override { size_t len = LR1110::getPacketLength(update); - if (len == 0) { - uint32_t irq = getIrqStatus(); - if (irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { - MESH_DEBUG_PRINTLN("LR1110: got header err, assuming shift"); - this->shiftCount += 4; // uint8 will loop around to 0 at 256, perfect as rx buffer is 256 bytes - } else { - MESH_DEBUG_PRINTLN("LR1110: got zero-length packet without header err irq"); - } + if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { + // we've just recieved 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 + MESH_DEBUG_PRINTLN("LR1110: got header err, calling standby()"); + standby(); } return len; } - int16_t readData(uint8_t *data, size_t len) override { - // check active modem - uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; - int16_t state = getPacketType(&modem); - RADIOLIB_ASSERT(state); - if((modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) && - (modem != RADIOLIB_LR11X0_PACKET_TYPE_GFSK)) { - return(RADIOLIB_ERR_WRONG_MODEM); - } - - // check integrity CRC - uint32_t irq = getIrqStatus(); - int16_t crcState = RADIOLIB_ERR_NONE; - // Report CRC mismatch when there's a payload CRC error, or a header error and no valid header (to avoid false alarm from previous packet) - if((irq & RADIOLIB_LR11X0_IRQ_CRC_ERR) || ((irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID))) { - crcState = RADIOLIB_ERR_CRC_MISMATCH; - } - - // get packet length - // the offset is needed since LR11x0 seems to move the buffer base by 4 bytes on every packet - uint8_t offset = 0; - size_t length = LR1110::getPacketLength(true, &offset); - if((len != 0) && (len < length)) { - // user requested less data than we got, only return what was requested - length = len; - } - - // read packet data - state = readBuffer8(data, length, (uint8_t)(offset + this->shiftCount)); // add shiftCount to offset - only change from radiolib - RADIOLIB_ASSERT(state); - - // clear the Rx buffer - state = clearRxBuffer(); - RADIOLIB_ASSERT(state); - - // clear interrupt flags - state = clearIrqState(RADIOLIB_LR11X0_IRQ_ALL); - - // check if CRC failed - this is done after reading data to give user the option to keep them - RADIOLIB_ASSERT(crcState); - - return(state); - } - RadioLibTime_t getTimeOnAir(size_t len) override { // calculate number of symbols float N_symbol = 0; From 0e259a63ed07099e9a31c377f077a3ab5dfd9793 Mon Sep 17 00:00:00 2001 From: Woodie-07 Date: Sat, 25 Oct 2025 22:12:30 +0100 Subject: [PATCH 054/409] lr1110 irq fixes fix incorrect irqs used in isReceiving. also remove getTimeOnAir override as fixed upstream --- src/helpers/radiolib/CustomLR1110.h | 60 +---------------------------- 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index cdf3cc7cf5..2e536de5ea 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -3,9 +3,6 @@ #include #include "MeshCore.h" -#define LR1110_IRQ_HAS_PREAMBLE 0b0000000100 // 4 4 valid LoRa header received -#define LR1110_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received - class CustomLR1110 : public LR1110 { public: CustomLR1110(Module *mod) : LR1110(mod) { } @@ -22,63 +19,10 @@ class CustomLR1110 : public LR1110 { } return len; } - - RadioLibTime_t getTimeOnAir(size_t len) override { - // calculate number of symbols - float N_symbol = 0; - if(this->codingRate <= RADIOLIB_LR11X0_LORA_CR_4_8_SHORT) { - // legacy coding rate - nice and simple - // get SF coefficients - float coeff1 = 0; - int16_t coeff2 = 0; - int16_t coeff3 = 0; - if(this->spreadingFactor < 7) { - // SF5, SF6 - coeff1 = 6.25; - coeff2 = 4*this->spreadingFactor; - coeff3 = 4*this->spreadingFactor; - } else if(this->spreadingFactor < 11) { - // SF7. SF8, SF9, SF10 - coeff1 = 4.25; - coeff2 = 4*this->spreadingFactor + 8; - coeff3 = 4*this->spreadingFactor; - } else { - // SF11, SF12 - coeff1 = 4.25; - coeff2 = 4*this->spreadingFactor + 8; - coeff3 = 4*(this->spreadingFactor - 2); - } - - // get CRC length - int16_t N_bitCRC = 16; - if(this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_DISABLED) { - N_bitCRC = 0; - } - - // get header length - int16_t N_symbolHeader = 20; - if(this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) { - N_symbolHeader = 0; - } - - // calculate number of LoRa preamble symbols - NO! Lora preamble is already in symbols - // uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4)); - - // calculate the number of symbols - nope - // N_symbol = (float)N_symbolPreamble + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4); - // calculate the number of symbols - using only preamblelora because it's already in symbols - N_symbol = (float)preambleLengthLoRa + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4); - } else { - // long interleaving - not needed for this modem - } - - // get time-on-air in us - return(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0f); -} - + bool isReceiving() { uint16_t irq = getIrqStatus(); - bool detected = ((irq & LR1110_IRQ_HEADER_VALID) || (irq & LR1110_IRQ_HAS_PREAMBLE)); + bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); return detected; } }; \ No newline at end of file From f339c74bb489e2394583f427642c21dbae3b5625 Mon Sep 17 00:00:00 2001 From: Matthias Wientapper Date: Mon, 27 Oct 2025 17:58:29 +0100 Subject: [PATCH 055/409] * Add #ifdef, reuse variable --- examples/companion_radio/ui-new/UITask.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 38c09781ec..35218ac2aa 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2,8 +2,9 @@ #include #include "../MyMesh.h" #include "target.h" -#include - +#ifdef WIFI_SSID + #include +#endif #ifndef AUTO_OFF_MILLIS #define AUTO_OFF_MILLIS 15000 // 15 seconds @@ -132,8 +133,6 @@ class HomeScreen : public UIScreen { int sensors_scroll_offset = 0; int next_sensors_refresh = 0; - char ipStr[20]; - void refresh_sensors() { if (millis() > next_sensors_refresh) { sensors_lpp.reset(); @@ -198,9 +197,9 @@ class HomeScreen : public UIScreen { #ifdef WIFI_SSID IPAddress ip = WiFi.localIP(); - snprintf(ipStr, sizeof(ipStr), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); display.setTextSize(1); - display.drawTextCentered(display.width() / 2, 54, ipStr); + display.drawTextCentered(display.width() / 2, 54, tmp); #endif if (_task->hasConnection()) { display.setColor(DisplayDriver::GREEN); From d4eb04d6e96ad2e776c5e1d7ec00a5efb6c67239 Mon Sep 17 00:00:00 2001 From: WattleFoxxo Date: Wed, 29 Oct 2025 15:20:31 +1100 Subject: [PATCH 056/409] Switch xiao rp2040 to std init --- variants/xiao_rp2040/target.cpp | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/variants/xiao_rp2040/target.cpp b/variants/xiao_rp2040/target.cpp index a801aae8bd..b7c1997580 100644 --- a/variants/xiao_rp2040/target.cpp +++ b/variants/xiao_rp2040/target.cpp @@ -20,34 +20,12 @@ SensorManager sensors; bool radio_init() { rtc_clock.begin(Wire); -#ifdef SX126X_DIO3_TCXO_VOLTAGE - float tcxo = SX126X_DIO3_TCXO_VOLTAGE; +#if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); #else - float tcxo = 1.6f; + return radio.std_init(); #endif - int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo); - - if (status != RADIOLIB_ERR_NONE) { - Serial.print("ERROR: radio init failed: "); - Serial.println(status); - return false; // fail - } - - radio.setCRC(1); - -#ifdef SX126X_CURRENT_LIMIT - radio.setCurrentLimit(SX126X_CURRENT_LIMIT); -#endif - -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif - -#ifdef SX126X_RX_BOOSTED_GAIN - radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif - - return true; // success } uint32_t radio_get_rng_seed() { From 81ab9446824aef47a3cb252f87d963c97784ddc4 Mon Sep 17 00:00:00 2001 From: Michael Hart Date: Tue, 7 Oct 2025 09:45:45 -0700 Subject: [PATCH 057/409] Adds serial commands to get stats - Added formatStatsReply, formatRadioStatsReply, and formatPacketStatsReply methods in MyMesh for both simple_repeater, simple_room_server, and simple_sensor. - Updated CommonCLI to handle new stats commands. --- examples/simple_repeater/MyMesh.cpp | 13 +++++++ examples/simple_repeater/MyMesh.h | 4 ++ examples/simple_room_server/MyMesh.cpp | 13 +++++++ examples/simple_room_server/MyMesh.h | 4 ++ examples/simple_sensor/SensorMesh.cpp | 13 +++++++ examples/simple_sensor/SensorMesh.h | 4 ++ src/helpers/CommonCLI.cpp | 6 +++ src/helpers/CommonCLI.h | 3 ++ src/helpers/StatsFormatHelper.h | 54 ++++++++++++++++++++++++++ 9 files changed, 114 insertions(+) create mode 100644 src/helpers/StatsFormatHelper.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index f328c752ea..abd284ffdd 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -787,6 +787,19 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { #endif } +void MyMesh::formatStatsReply(char *reply) { + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); +} + +void MyMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void MyMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index a9ab251ee5..694e8ff9ee 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #ifdef WITH_BRIDGE @@ -183,6 +184,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void setTxPower(uint8_t power_dbm) override; void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; + void formatStatsReply(char *reply) override; + void formatRadioStatsReply(char *reply) override; + void formatPacketStatsReply(char *reply) override; mesh::LocalIdentity& getSelfId() override { return self_id; } diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 5d245ba164..592861bfe9 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -729,6 +729,19 @@ void MyMesh::clearStats() { ((SimpleMeshTables *)getTables())->resetStats(); } +void MyMesh::formatStatsReply(char *reply) { + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); +} + +void MyMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void MyMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { while (*command == ' ') command++; // skip leading spaces diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 88e30bf27b..d149b37b1f 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -192,6 +193,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); } + void formatStatsReply(char *reply) override; + void formatRadioStatsReply(char *reply) override; + void formatPacketStatsReply(char *reply) override; mesh::LocalIdentity& getSelfId() override { return self_id; } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 8f8a11fed3..f914a6b6ce 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -769,6 +769,19 @@ void SensorMesh::setTxPower(uint8_t power_dbm) { radio_set_tx_power(power_dbm); } +void SensorMesh::formatStatsReply(char *reply) { + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); +} + +void SensorMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void SensorMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + float SensorMesh::getTelemValue(uint8_t channel, uint8_t type) { auto buf = telemetry.getBuffer(); uint8_t size = telemetry.getSize(); diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index cdc3940c30..ba55bc7011 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,9 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); } + void formatStatsReply(char *reply) override; + void formatRadioStatsReply(char *reply) override; + void formatPacketStatsReply(char *reply) override; mesh::LocalIdentity& getSelfId() override { return self_id; } void saveIdentity(const mesh::LocalIdentity& new_id) override; void clearStats() override { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b8bb698a5c..eac2698ee0 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -663,6 +663,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) { _callbacks->dumpLogFile(); strcpy(reply, " EOF"); + } else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) { + _callbacks->formatPacketStatsReply(reply); + } else if (sender_timestamp == 0 && memcmp(command, "stats-radio", 11) == 0 && (command[11] == 0 || command[11] == ' ')) { + _callbacks->formatRadioStatsReply(reply); + } else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) { + _callbacks->formatStatsReply(reply); } else { strcpy(reply, "Unknown command"); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ea59aa92dd..3cfca46c80 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -66,6 +66,9 @@ class CommonCLICallbacks { virtual void removeNeighbor(const uint8_t* pubkey, int key_len) { // no op by default }; + virtual void formatStatsReply(char *reply) = 0; + virtual void formatRadioStatsReply(char *reply) = 0; + virtual void formatPacketStatsReply(char *reply) = 0; virtual mesh::LocalIdentity& getSelfId() = 0; virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; diff --git a/src/helpers/StatsFormatHelper.h b/src/helpers/StatsFormatHelper.h new file mode 100644 index 0000000000..d0107f3b30 --- /dev/null +++ b/src/helpers/StatsFormatHelper.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Mesh.h" + +class StatsFormatHelper { +public: + static void formatCoreStats(char* reply, + mesh::MainBoard& board, + mesh::MillisecondClock& ms, + uint16_t err_flags, + mesh::PacketManager* mgr) { + sprintf(reply, + "{\"battery_mv\":%u,\"uptime_secs\":%u,\"errors\":%u,\"queue_len\":%u}", + board.getBattMilliVolts(), + ms.getMillis() / 1000, + err_flags, + mgr->getOutboundCount(0xFFFFFFFF) + ); + } + + template + static void formatRadioStats(char* reply, + mesh::Radio* radio, + RadioDriverType& driver, + uint32_t total_air_time_ms, + uint32_t total_rx_air_time_ms) { + sprintf(reply, + "{\"noise_floor\":%d,\"last_rssi\":%d,\"last_snr\":%.2f,\"tx_air_secs\":%u,\"rx_air_secs\":%u}", + (int16_t)radio->getNoiseFloor(), + (int16_t)driver.getLastRSSI(), + driver.getLastSNR(), + total_air_time_ms / 1000, + total_rx_air_time_ms / 1000 + ); + } + + template + static void formatPacketStats(char* reply, + RadioDriverType& driver, + uint32_t n_sent_flood, + uint32_t n_sent_direct, + uint32_t n_recv_flood, + uint32_t n_recv_direct) { + sprintf(reply, + "{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u}", + driver.getPacketsRecv(), + driver.getPacketsSent(), + n_sent_flood, + n_sent_direct, + n_recv_flood, + n_recv_direct + ); + } +}; From 1bbc2151f10b4ee07344ee613234bb63d6fdd8c0 Mon Sep 17 00:00:00 2001 From: recrof Date: Wed, 29 Oct 2025 10:32:39 +0100 Subject: [PATCH 058/409] remove vision master boards because of issues with display drivers --- variants/heltec_e213/platformio.ini | 12 ++++++------ variants/heltec_e290/platformio.ini | 12 ++++++------ variants/heltec_t190/platformio.ini | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 74598a2dc0..41824e1cfe 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -40,7 +40,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_E213_companion_radio_ble] +[env:Heltec_E213_companion_ble] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -60,7 +60,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E213_companion_radio_usb] +[env:Heltec_E213_companion_usb] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -78,7 +78,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E213_repeater] +[env:Heltec_E213_rptr] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -95,7 +95,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_E213_repeater_bridge_rs232] +; [env:Heltec_E213_rptr_bridge_rs232] ; extends = Heltec_E213_base ; build_flags = ; ${Heltec_E213_base.build_flags} @@ -119,7 +119,7 @@ lib_deps = ; ${Heltec_E213_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_E213_repeater_bridge_espnow] +[env:Heltec_E213_rptr_bridge_espnow] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -141,7 +141,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_E213_room_server] +[env:Heltec_E213_room_svr] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 3289c975e2..ccc81b4edf 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -34,7 +34,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_E290_companion_radio_ble] +[env:Heltec_E290_companion_ble] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -54,7 +54,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E290_companion_radio_usb] +[env:Heltec_E290_companion_usb] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -74,7 +74,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E290_repeater] +[env:Heltec_E290_rptr] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -91,7 +91,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_E290_repeater_bridge_rs232] +; [env:Heltec_E290_rptr_bridge_rs232] ; extends = Heltec_E290_base ; build_flags = ; ${Heltec_E290_base.build_flags} @@ -115,7 +115,7 @@ lib_deps = ; ${Heltec_E290_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_E290_repeater_bridge_espnow] +[env:Heltec_E290_rptr_bridge_espnow] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -137,7 +137,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_E290_room_server] +[env:Heltec_E290_room_svr] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 72a40dec34..b27e7f2b1d 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -47,7 +47,7 @@ lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit GFX Library @ ^1.12.1 -[env:Heltec_T190_companion_radio_ble] +[env:Heltec_T190_companion_ble] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -65,7 +65,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_T190_companion_radio_usb] +[env:Heltec_T190_companion_usb] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -81,7 +81,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_T190_repeater] +[env:Heltec_T190_rptr] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -96,7 +96,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_T190_repeater_bridge_rs232] +; [env:Heltec_T190_rptr_bridge_rs232] ; extends = Heltec_T190_base ; build_flags = ; ${Heltec_T190_base.build_flags} @@ -118,7 +118,7 @@ lib_deps = ; ${Heltec_T190_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_T190_repeater_bridge_espnow] +[env:Heltec_T190_rptr_bridge_espnow] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -138,7 +138,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_T190_room_server] +[env:Heltec_T190_room_svr] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} From 1c052d8ad2097bbb72816d9e07dc93b2e0924090 Mon Sep 17 00:00:00 2001 From: recrof Date: Wed, 29 Oct 2025 13:14:27 +0100 Subject: [PATCH 059/409] use different strategy in renaming the envs in order to avoid building --- variants/heltec_e213/platformio.ini | 12 ++++++------ variants/heltec_e290/platformio.ini | 10 +++++----- variants/heltec_t190/platformio.ini | 12 ++++++------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 41824e1cfe..93bdc00034 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -40,7 +40,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_E213_companion_ble] +[env:Heltec_E213_companion_radio_ble_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -60,7 +60,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E213_companion_usb] +[env:Heltec_E213_companion_radio_usb_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -78,7 +78,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E213_rptr] +[env:Heltec_E213_repeater_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -95,7 +95,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_E213_rptr_bridge_rs232] +; [env:Heltec_E213_repeater_bridge_rs232_] ; extends = Heltec_E213_base ; build_flags = ; ${Heltec_E213_base.build_flags} @@ -119,7 +119,7 @@ lib_deps = ; ${Heltec_E213_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_E213_rptr_bridge_espnow] +[env:Heltec_E213_repeater_bridge_espnow_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -141,7 +141,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_E213_room_svr] +[env:Heltec_E213_room_server_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index ccc81b4edf..2039b5267e 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -34,7 +34,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_E290_companion_ble] +[env:Heltec_E290_companion_ble_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -54,7 +54,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E290_companion_usb] +[env:Heltec_E290_companion_usb_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -91,7 +91,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_E290_rptr_bridge_rs232] +; [env:Heltec_E290_repeater_bridge_rs232_] ; extends = Heltec_E290_base ; build_flags = ; ${Heltec_E290_base.build_flags} @@ -115,7 +115,7 @@ lib_deps = ; ${Heltec_E290_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_E290_rptr_bridge_espnow] +[env:Heltec_E290_repeater_bridge_espnow_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -137,7 +137,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_E290_room_svr] +[env:Heltec_E290_room_server_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index b27e7f2b1d..01e9dfc917 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -47,7 +47,7 @@ lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit GFX Library @ ^1.12.1 -[env:Heltec_T190_companion_ble] +[env:Heltec_T190_companion_radio_ble_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -65,7 +65,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_T190_companion_usb] +[env:Heltec_T190_companion_radio_usb_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -81,7 +81,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_T190_rptr] +[env:Heltec_T190_repeater_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -96,7 +96,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_T190_rptr_bridge_rs232] +; [env:Heltec_T190_repeater_bridge_rs232_] ; extends = Heltec_T190_base ; build_flags = ; ${Heltec_T190_base.build_flags} @@ -118,7 +118,7 @@ lib_deps = ; ${Heltec_T190_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_T190_rptr_bridge_espnow] +[env:Heltec_T190_repeater_bridge_espnow_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -138,7 +138,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_T190_room_svr] +[env:Heltec_T190_room_server_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} From 377f9ff67dc4902609904199f10f98cf7dbc747b Mon Sep 17 00:00:00 2001 From: recrof Date: Wed, 29 Oct 2025 13:22:11 +0100 Subject: [PATCH 060/409] renamed esp32c6 variants, so they are not included in release. added disclaimer about pioarduino builds --- platformio.ini | 1 + variants/lilygo_tlora_c6/platformio.ini | 6 +++--- variants/xiao_c6/platformio.ini | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/platformio.ini b/platformio.ini index b5bce215ee..b1dd2367d9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -67,6 +67,7 @@ lib_deps = file://arch/esp32/AsyncElegantOTA ; esp32c6 uses arduino framework 3.x +; WARNING: experimental. pioarduino on esp32c6 needs work - it's not considered stable and has issues. [esp32c6_base] extends = esp32_base platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index 308c85eb3a..88a811b5a5 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -30,7 +30,7 @@ build_flags = build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/lilygo_tlora_c6> -[env:LilyGo_Tlora_C6_repeater] +[env:LilyGo_Tlora_C6_repeater_] extends = tlora_c6 build_src_filter = ${tlora_c6.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -47,7 +47,7 @@ lib_deps = ${tlora_c6.lib_deps} ; ${esp32_ota.lib_deps} -[env:LilyGo_Tlora_C6_room_server] +[env:LilyGo_Tlora_C6_room_server_] extends = tlora_c6 build_src_filter = ${tlora_c6.build_src_filter} +<../examples/simple_room_server> @@ -64,7 +64,7 @@ lib_deps = ${tlora_c6.lib_deps} ; ${esp32_ota.lib_deps} -[env:LilyGo_Tlora_C6_companion_radio_ble] +[env:LilyGo_Tlora_C6_companion_radio_ble_] extends = tlora_c6 build_flags = ${tlora_c6.build_flags} -D MAX_CONTACTS=300 diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 6e963432f2..9c9710835a 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -30,7 +30,7 @@ build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/xiao_c6> + -[env:Xiao_C6_repeater] +[env:Xiao_C6_repeater_] extends = Xiao_C6 build_src_filter = ${Xiao_C6.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -47,7 +47,7 @@ lib_deps = ${Xiao_C6.lib_deps} ; ${esp32_ota.lib_deps} -[env:Xiao_C6_companion_radio_ble] +[env:Xiao_C6_companion_radio_ble_] extends = Xiao_C6 build_flags = ${Xiao_C6.build_flags} -D MAX_CONTACTS=300 @@ -90,10 +90,10 @@ build_flags = -D SX126X_RX_BOOSTED_GAIN=1 -D USE_XIAO_ESP32C6_EXTERNAL_ANTENNA=1 -[env:Meshimi_repeater] +[env:Meshimi_repeater_] extends = Meshimi build_src_filter = ${Meshimi.build_src_filter} - +<../examples/simple_repeater/*.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Meshimi.build_flags} -D ADVERT_NAME='"Meshimi Repeater"' @@ -104,7 +104,7 @@ build_flags = lib_deps = ${Meshimi.lib_deps} -[env:Meshimi_companion_radio_ble] +[env:Meshimi_companion_radio_ble_] extends = Meshimi build_flags = ${Meshimi.build_flags} -D MAX_CONTACTS=300 @@ -115,9 +115,9 @@ build_flags = ${Meshimi.build_flags} -D ENABLE_PRIVATE_KEY_IMPORT=1 -D ENABLE_PRIVATE_KEY_EXPORT=1 build_src_filter = ${Meshimi.build_src_filter} - + - - - +<../examples/companion_radio/*.cpp> + + + - + +<../examples/companion_radio/*.cpp> lib_deps = ${Meshimi.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -147,7 +147,7 @@ build_flags = -USX126X_DIO2_AS_RF_SWITCH -USX126X_DIO3_TCXO_VOLTAGE -[env:WHY2025_badge_repeater] +[env:WHY2025_badge_repeater_] extends = WHY2025_badge build_src_filter = ${WHY2025_badge.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -164,7 +164,7 @@ lib_deps = ${WHY2025_badge.lib_deps} ; ${esp32_ota.lib_deps} -[env:WHY2025_badge_companion_radio_ble] +[env:WHY2025_badge_companion_radio_ble_] extends = WHY2025_badge build_flags = ${WHY2025_badge.build_flags} -D MAX_CONTACTS=300 From 4aef69662074674da99e431887c5202b154351aa Mon Sep 17 00:00:00 2001 From: recrof Date: Wed, 29 Oct 2025 13:27:26 +0100 Subject: [PATCH 061/409] missed one definition --- variants/heltec_e290/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 2039b5267e..201b3631d3 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -74,7 +74,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E290_rptr] +[env:Heltec_E290_repeater_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} From 8cbcd2271d586997dd3f92011e6c024477c36798 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 29 Oct 2025 23:35:46 +1100 Subject: [PATCH 062/409] * experimental: retransmit delay, removing the 6 'slots' --- examples/simple_repeater/MyMesh.cpp | 4 ++-- examples/simple_room_server/MyMesh.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index abd284ffdd..9a9745658e 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -397,11 +397,11 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 6) * t; + return getRNG()->nextInt(0, 5*t); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 6) * t; + return getRNG()->nextInt(0, 5*t); } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 592861bfe9..cc65780026 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -262,11 +262,11 @@ const char *MyMesh::getLogDateTime() { uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 6) * t; + return getRNG()->nextInt(0, 5*t); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 6) * t; + return getRNG()->nextInt(0, 5*t); } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { From 80f0405600d4510f89742b9694f8802150324fb1 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 30 Oct 2025 00:03:10 +1100 Subject: [PATCH 063/409] * direct.txdelay default now 0.2 (was zero) --- examples/simple_repeater/MyMesh.cpp | 1 + examples/simple_room_server/MyMesh.cpp | 1 + examples/simple_sensor/SensorMesh.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 9a9745658e..9c9471f529 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -610,6 +610,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f + _prefs.direct_tx_delay_factor = 0.2f; // was zero StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index cc65780026..a6ccb1402b 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -593,6 +593,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // off by default, was 10.0 _prefs.tx_delay_factor = 0.5f; // was 0.25f; + _prefs.direct_tx_delay_factor = 0.2f; // was zero StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index f914a6b6ce..6564c4ef9c 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -664,6 +664,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f + _prefs.direct_tx_delay_factor = 0.2f; // was zero StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; From 3d9378d91eab4ff94e6225e8ac6d357743ee9684 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 30 Oct 2025 16:45:50 +1100 Subject: [PATCH 064/409] * Fix for VolatileRTCClock wrapping around to initial synced time every 49 days --- examples/companion_radio/main.cpp | 1 + examples/simple_repeater/main.cpp | 1 + examples/simple_room_server/main.cpp | 1 + examples/simple_secure_chat/main.cpp | 3 ++- examples/simple_sensor/main.cpp | 1 + src/MeshCore.h | 5 +++++ src/helpers/ArduinoHelpers.h | 16 ++++++++++++---- src/helpers/AutoDiscoverRTCClock.h | 4 ++++ 8 files changed, 27 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 89adca5970..82c8c21d93 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -227,4 +227,5 @@ void loop() { #ifdef DISPLAY_CLASS ui_task.loop(); #endif + rtc_clock.tick(); } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7db0e7d47e..5843df748c 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -114,4 +114,5 @@ void loop() { #ifdef DISPLAY_CLASS ui_task.loop(); #endif + rtc_clock.tick(); } diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 8f6b6d58df..1a3b4d6e0f 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -110,4 +110,5 @@ void loop() { #ifdef DISPLAY_CLASS ui_task.loop(); #endif + rtc_clock.tick(); } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index eac358980d..da1bac5b32 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -548,7 +548,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { StdRNG fast_rng; SimpleMeshTables tables; -MyMesh the_mesh(radio_driver, fast_rng, *new VolatileRTCClock(), tables); // TODO: test with 'rtc_clock' in target.cpp +MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables); void halt() { while (1) ; @@ -587,4 +587,5 @@ void setup() { void loop() { the_mesh.loop(); + rtc_clock.tick(); } diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index 2dacd1b47a..a5fcc1484f 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -144,4 +144,5 @@ void loop() { #ifdef DISPLAY_CLASS ui_task.loop(); #endif + rtc_clock.tick(); } diff --git a/src/MeshCore.h b/src/MeshCore.h index 5c7e176076..94bf351d82 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -72,6 +72,11 @@ class RTCClock { */ virtual void setCurrentTime(uint32_t time) = 0; + /** + * override in classes that need to periodically update internal state + */ + virtual void tick() { /* no op */} + uint32_t getCurrentTimeUnique() { uint32_t t = getCurrentTime(); if (t <= last_unique) { diff --git a/src/helpers/ArduinoHelpers.h b/src/helpers/ArduinoHelpers.h index a736c9b0be..97596daa31 100644 --- a/src/helpers/ArduinoHelpers.h +++ b/src/helpers/ArduinoHelpers.h @@ -4,11 +4,19 @@ #include class VolatileRTCClock : public mesh::RTCClock { - long millis_offset; + uint32_t base_time; + uint64_t accumulator; + unsigned long prev_millis; public: - VolatileRTCClock() { millis_offset = 1715770351; } // 15 May 2024, 8:50pm - uint32_t getCurrentTime() override { return (millis()/1000 + millis_offset); } - void setCurrentTime(uint32_t time) override { millis_offset = time - millis()/1000; } + VolatileRTCClock() { base_time = 1715770351; accumulator = 0; prev_millis = millis(); } // 15 May 2024, 8:50pm + uint32_t getCurrentTime() override { return base_time + accumulator/1000; } + void setCurrentTime(uint32_t time) override { base_time = time; accumulator = 0; prev_millis = millis(); } + + void tick() override { + unsigned long now = millis(); + accumulator += (now - prev_millis); + prev_millis = now; + } }; class ArduinoMillis : public mesh::MillisecondClock { diff --git a/src/helpers/AutoDiscoverRTCClock.h b/src/helpers/AutoDiscoverRTCClock.h index 02eedf5235..11364cd811 100644 --- a/src/helpers/AutoDiscoverRTCClock.h +++ b/src/helpers/AutoDiscoverRTCClock.h @@ -14,4 +14,8 @@ class AutoDiscoverRTCClock : public mesh::RTCClock { void begin(TwoWire& wire); uint32_t getCurrentTime() override; void setCurrentTime(uint32_t time) override; + + void tick() override { + _fallback->tick(); // is typically VolatileRTCClock, which now needs tick() + } }; From f3b20d5e70e67780decc64c69a7e34b5b14356cc Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:35:01 +0100 Subject: [PATCH 065/409] t114 gps --- variants/heltec_t114/platformio.ini | 5 +++++ variants/heltec_t114/target.cpp | 2 +- variants/heltec_t114/variant.h | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index c482a30ad6..91ca78cd46 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -29,6 +29,11 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D DISPLAY_CLASS=NullDisplayDriver -D ST7789 + -D PIN_GPS_RX=39 + -D PIN_GPS_TX=37 + -D PIN_GPS_EN=21 + -D PIN_GPS_RESET=38 + -D PIN_GPS_RESET_ACTIVE=LOW build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/heltec_t114> diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index 5b786437db..c3341103a5 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -11,7 +11,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); T114SensorManager sensors = T114SensorManager(nmea); #ifdef DISPLAY_CLASS diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index a0fd2e4f67..b3f760bbb0 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -117,6 +117,8 @@ #define GPS_EN (21) #define GPS_RESET (38) +#define PIN_GPS_RX (39) // This is for bits going TOWARDS the GPS +#define PIN_GPS_TX (37) // This is for bits going TOWARDS the CPU //////////////////////////////////////////////////////////////////////////////// // TFT From 96e786fa9e17b1f35406830aa812c60aa812f283 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 30 Oct 2025 19:11:04 +1100 Subject: [PATCH 066/409] * FIX: for divide by zero crash --- examples/simple_repeater/MyMesh.cpp | 4 ++-- examples/simple_room_server/MyMesh.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 9c9471f529..5e62ab6df9 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -397,11 +397,11 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 5*t); + return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 5*t); + return getRNG()->nextInt(0, 5*t + 1); } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index a6ccb1402b..4d953c9cde 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -262,11 +262,11 @@ const char *MyMesh::getLogDateTime() { uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 5*t); + return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 5*t); + return getRNG()->nextInt(0, 5*t + 1); } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { From 5088444f858a3fe7135ed5f77892be69285c76a6 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Thu, 30 Oct 2025 16:33:02 +0100 Subject: [PATCH 067/409] Update Wio WM1110 configuration to disable GPS and clean up location provider code --- variants/wio_wm1110/platformio.ini | 1 + variants/wio_wm1110/target.cpp | 20 +------------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/variants/wio_wm1110/platformio.ini b/variants/wio_wm1110/platformio.ini index 313430ff72..ec65e706b3 100644 --- a/variants/wio_wm1110/platformio.ini +++ b/variants/wio_wm1110/platformio.ini @@ -24,6 +24,7 @@ build_flags = ${nrf52_base.build_flags} -D LR11X0_DIO_AS_RF_SWITCH=true -D LR11X0_DIO3_TCXO_VOLTAGE=1.8 -D RF_SWITCH_TABLE + -D ENV_INCLUDE_GPS=0 build_src_filter = ${nrf52_base.build_src_filter} + + diff --git a/variants/wio_wm1110/target.cpp b/variants/wio_wm1110/target.cpp index cc302a852f..c659d708bb 100644 --- a/variants/wio_wm1110/target.cpp +++ b/variants/wio_wm1110/target.cpp @@ -1,23 +1,6 @@ #include #include "target.h" #include -#include - -class WM1110LocationProvider : public LocationProvider { -public: - long getLatitude() override { return 0; } - long getLongitude() override { return 0; } - long getAltitude() override { return 0; } - long satellitesCount() override { return 0; } - bool isValid() override { return false; } - long getTimestamp() override { return 0; } - void sendSentence(const char* sentence) override {} - void reset() override {} - void begin() override {} - void stop() override {} - void loop() override {} - bool isEnabled() override { return false; } -}; WioWM1110Board board; @@ -26,8 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; -WM1110LocationProvider location_provider; -EnvironmentSensorManager sensors(location_provider); +EnvironmentSensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 From 0b8159c6e51e0bdd2098b19c049d8fdd8de0afc5 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 31 Oct 2025 11:42:36 +1100 Subject: [PATCH 068/409] refactor DataStore to use openRead() and openWrite() refactored loadPrefsInt(), loadContacts(), loadChannels(), getBlobByKey() and putBlobByKey() to use openRead() and openWrite() --- examples/companion_radio/DataStore.cpp | 40 ++++---------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index f1adb05ef5..4441d2910a 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -197,11 +197,7 @@ void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon) } void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& node_lat, double& node_lon) { -#if defined(RP2040_PLATFORM) - File file = _fs->open(filename, "r"); -#else - File file = _fs->open(filename); -#endif + File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; @@ -262,16 +258,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ } void DataStore::loadContacts(DataStoreHost* host) { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - if (_getContactsChannelsFS()->exists("/contacts3")) { - File file = _getContactsChannelsFS()->open("/contacts3"); -#elif defined(RP2040_PLATFORM) - if (_fs->exists("/contacts3")) { - File file = _fs->open("/contacts3", "r"); -#else - if (_fs->exists("/contacts3")) { - File file = _fs->open("/contacts3", "r", false); -#endif +File file = openRead(_getContactsChannelsFS(), "/contacts3"); if (file) { bool full = false; while (!full) { @@ -299,7 +286,6 @@ void DataStore::loadContacts(DataStoreHost* host) { } file.close(); } - } } void DataStore::saveContacts(DataStoreHost* host) { @@ -332,16 +318,7 @@ void DataStore::saveContacts(DataStoreHost* host) { } void DataStore::loadChannels(DataStoreHost* host) { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - if (_getContactsChannelsFS()->exists("/channels2")) { - File file = _getContactsChannelsFS()->open("/channels2"); -#elif defined(RP2040_PLATFORM) - if (_fs->exists("/channels2")) { - File file = _fs->open("/channels2", "r"); -#else - if (_fs->exists("/channels2")) { - File file = _fs->open("/channels2", "r", false); -#endif + File file = openRead(_getContactsChannelsFS(), "/channels2"); if (file) { bool full = false; uint8_t channel_idx = 0; @@ -363,7 +340,6 @@ void DataStore::loadChannels(DataStoreHost* host) { } file.close(); } - } } void DataStore::saveChannels(DataStoreHost* host) { @@ -520,7 +496,7 @@ void DataStore::migrateToSecondaryFS() { } uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { - File file = _getContactsChannelsFS()->open("/adv_blobs"); + File file = openRead(_getContactsChannelsFS(), "/adv_blobs"); uint8_t len = 0; // 0 = not found if (file) { BlobRec tmp; @@ -539,7 +515,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { if (len < PUB_KEY_SIZE+4+SIGNATURE_SIZE || len > MAX_ADVERT_PKT_LEN) return false; checkAdvBlobFile(); - File file = _getContactsChannelsFS()->open("/adv_blobs", FILE_O_WRITE); + File file = openWrite(_getContactsChannelsFS(), "/adv_blobs"); if (file) { uint32_t pos = 0, found_pos = 0; uint32_t min_timestamp = 0xFFFFFFFF; @@ -583,11 +559,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b sprintf(path, "/bl/%s", fname); if (_fs->exists(path)) { -#if defined(RP2040_PLATFORM) - File f = _fs->open(path, "r"); -#else - File f = _fs->open(path); -#endif + File f = openRead(_fs, path); if (f) { int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!! f.close(); From 52a3df4977f1a775324814f3115d539a4f6bd6f8 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 31 Oct 2025 15:06:29 +1100 Subject: [PATCH 069/409] revert pubBlobByKey() change --- examples/companion_radio/DataStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 4441d2910a..eac027b89c 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -515,7 +515,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { if (len < PUB_KEY_SIZE+4+SIGNATURE_SIZE || len > MAX_ADVERT_PKT_LEN) return false; checkAdvBlobFile(); - File file = openWrite(_getContactsChannelsFS(), "/adv_blobs"); + File file = _getContactsChannelsFS()->open("/adv_blobs", FILE_O_WRITE); if (file) { uint32_t pos = 0, found_pos = 0; uint32_t min_timestamp = 0xFFFFFFFF; From 7abe6c969382f989e8e160f617073de0e58c65aa Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 31 Oct 2025 16:54:58 +1100 Subject: [PATCH 070/409] * Upping max channel hash conflicts to 4 (was 2) --- src/Mesh.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index b055d81146..a480a9c330 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -201,9 +201,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (i + 2 >= pkt->payload_len) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); } else if (!_tables->hasSeen(pkt)) { - // scan channels DB, for all matching hashes of 'channel_hash' (max 2 matches supported ATM) - GroupChannel channels[2]; - int num = searchChannelsByHash(&channel_hash, channels, 2); + // scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM) + GroupChannel channels[4]; + int num = searchChannelsByHash(&channel_hash, channels, 4); // for each matching channel, try to decrypt data for (int j = 0; j < num; j++) { // decrypt, checking MAC is valid From 7755400a357dcc32eeaded3124735688e5787e93 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 31 Oct 2025 20:40:22 +1100 Subject: [PATCH 071/409] * Companion: Now using transport codes { 0, 0 } when Share contact zero hop. * Repeater: onAdvertRecv(), adverts via Share now NOT added to neighbours table --- examples/simple_repeater/MyMesh.cpp | 11 +++++++++-- src/Mesh.cpp | 13 +++++++++++++ src/Mesh.h | 6 ++++++ src/helpers/BaseChatMesh.cpp | 15 ++++++++++++--- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5e62ab6df9..1afad18b95 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -448,12 +448,19 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { } } +static bool isShare(const mesh::Packet *packet) { + if (packet->hasTransportCodes()) { + return packet->transport_codes[0] == 0 && packet->transport_codes[1] == 0; // codes { 0, 0 } means 'send to nowhere' + } + return false; +} + void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32_t timestamp, const uint8_t *app_data, size_t app_data_len) { mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl - // if this a zero hop advert, add it to neighbours - if (packet->path_len == 0) { + // if this a zero hop advert (and not via 'Share'), add it to neighbours + if (packet->path_len == 0 && !isShare(packet)) { AdvertDataParser parser(app_data, app_data_len); if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters putNeighbour(id, timestamp, packet->getSNR()); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index a480a9c330..53dc74f5d5 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -645,4 +645,17 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) { sendPacket(packet, 0, delay_millis); } +void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) { + packet->header &= ~PH_ROUTE_MASK; + packet->header |= ROUTE_TYPE_TRANSPORT_DIRECT; + packet->transport_codes[0] = transport_codes[0]; + packet->transport_codes[1] = transport_codes[1]; + + packet->path_len = 0; // path_len of zero means Zero Hop + + _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + + sendPacket(packet, 0, delay_millis); +} + } \ No newline at end of file diff --git a/src/Mesh.h b/src/Mesh.h index a8fdb2a4f5..cbf1c9cf07 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -196,6 +196,12 @@ class Mesh : public Dispatcher { */ void sendZeroHop(Packet* packet, uint32_t delay_millis=0); + /** + * \brief send a locally-generated Packet to just neigbor nodes (zero hops), with specific transort codes + * \param transport_codes array of 2 codes to attach to packet + */ + void sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); + }; } diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index e03dd088fc..9b1eb1ce78 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -68,9 +68,16 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, } // save a copy of raw advert packet (to support "Share..." function) - int plen = packet->writeTo(temp_buf); + int plen; + { + uint8_t save = packet->header; + packet->header &= ~PH_ROUTE_MASK; + packet->header |= ROUTE_TYPE_FLOOD; // make sure transport codes are NOT saved + plen = packet->writeTo(temp_buf); + packet->header = save; + } putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); - + bool is_new = false; if (from == NULL) { if (!isAutoAddEnabled()) { @@ -405,7 +412,9 @@ bool BaseChatMesh::shareContactZeroHop(const ContactInfo& contact) { if (packet == NULL) return false; // no Packets available packet->readFrom(temp_buf, plen); // restore Packet from 'blob' - sendZeroHop(packet); + uint16_t codes[2]; + codes[0] = codes[1] = 0; // { 0, 0 } means 'send this nowhere' + sendZeroHop(packet, codes); return true; // success } From c13b4ae4816c6008d866b22e382955883953cde5 Mon Sep 17 00:00:00 2001 From: Adam Mealings Date: Fri, 31 Oct 2025 13:04:59 +0000 Subject: [PATCH 072/409] Analogue button delay based on millis --- examples/companion_radio/ui-new/UITask.cpp | 21 ++++++++++++--------- examples/companion_radio/ui-new/UITask.h | 4 ++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a08928..086f825999 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -700,15 +700,18 @@ void UITask::loop() { } #endif #if defined(PIN_USER_BTN_ANA) - ev = analog_btn.check(); - if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_NEXT); - } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); - } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { - c = handleDoubleClick(KEY_PREV); - } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + if (abs(millis() - _analogue_pin_read_millis) > 10) { + ev = analog_btn.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_NEXT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_ENTER); + } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { + c = handleDoubleClick(KEY_PREV); + } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { + c = handleTripleClick(KEY_SELECT); + } + _analogue_pin_read_millis = millis(); } #endif #if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN) diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index c24d33a48b..32d5f3a0dc 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -40,6 +40,10 @@ class UITask : public AbstractUITask { int last_led_increment = 0; #endif +#ifdef PIN_USER_BTN_ANA + unsigned long _analogue_pin_read_millis = millis(); +#endif + UIScreen* splash; UIScreen* home; UIScreen* msg_preview; From ff4fa7be31d5668126ce84aa770da420042f71b0 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Fri, 31 Oct 2025 14:42:16 +0100 Subject: [PATCH 073/409] Add ESP32-S3-Zero board configuration and Nibble Screen Connect variant --- boards/esp32-s3-zero.json | 40 +++++ variants/nibble_screen_connect/platformio.ini | 159 ++++++++++++++++++ variants/nibble_screen_connect/target.cpp | 49 ++++++ variants/nibble_screen_connect/target.h | 30 ++++ 4 files changed, 278 insertions(+) create mode 100644 boards/esp32-s3-zero.json create mode 100644 variants/nibble_screen_connect/platformio.ini create mode 100644 variants/nibble_screen_connect/target.cpp create mode 100644 variants/nibble_screen_connect/target.h diff --git a/boards/esp32-s3-zero.json b/boards/esp32-s3-zero.json new file mode 100644 index 0000000000..7a9dbc5303 --- /dev/null +++ b/boards/esp32-s3-zero.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "-D ARDUINO_USB_CDC_ON_BOOT=1", + "-D ARDUINO_USB_MSC_ON_BOOT=0", + "-D ARDUINO_USB_DFU_ON_BOOT=0", + "-D ARDUINO_USB_MODE=1", + "-D ARDUINO_RUNNING_CORE=1", + "-D ARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "ESP32-S3-Zero", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.espressif.com", + "vendor": "Espressif" +} + diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini new file mode 100644 index 0000000000..e18bcde161 --- /dev/null +++ b/variants/nibble_screen_connect/platformio.ini @@ -0,0 +1,159 @@ +[nibble_screen_connect_base] +extends = esp32_base +board = esp32-s3-zero +build_flags = + ${esp32_base.build_flags} + -I variants/nibble_screen_connect + -D NIBBLE_SCREEN_CONNECT + -D P_LORA_DIO_1=4 + -D P_LORA_NSS=10 + -D P_LORA_RESET=6 + -D P_LORA_BUSY=5 + -D P_LORA_SCLK=13 + -D P_LORA_MISO=12 + -D P_LORA_MOSI=11 + -D PIN_USER_BTN=1 + -D PIN_BOARD_SDA=8 + -D PIN_BOARD_SCL=7 + -D HAS_NEOPIXEL + -D NEOPIXEL_COUNT=1 + -D NEOPIXEL_DATA=21 + -D NEOPIXEL_TYPE=(NEO_GRB+NEO_KHZ800) + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/nibble_screen_connect> +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + adafruit/Adafruit NeoPixel @ ^1.12.3 + +[env:nibble_screen_connect_repeater] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Nibble Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_repeater_bridge_espnow] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_terminal_chat] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=1 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_room_server] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Nibble Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_companion_radio_usb] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_companion_radio_ble] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_companion_radio_wifi] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + diff --git a/variants/nibble_screen_connect/target.cpp b/variants/nibble_screen_connect/target.cpp new file mode 100644 index 0000000000..1980e0394c --- /dev/null +++ b/variants/nibble_screen_connect/target.cpp @@ -0,0 +1,49 @@ +#include +#include "target.h" + +ESP32Board board; + +static SPIClass spi; +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + return radio.std_init(&spi); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} + diff --git a/variants/nibble_screen_connect/target.h b/variants/nibble_screen_connect/target.h new file mode 100644 index 0000000000..66e69901a0 --- /dev/null +++ b/variants/nibble_screen_connect/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ESP32Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + From d0caa3be0447bc3eecc3d2a301c56021ba6a3734 Mon Sep 17 00:00:00 2001 From: Devin Carraway Date: Fri, 31 Oct 2025 22:11:24 -0700 Subject: [PATCH 074/409] Fix the sample RAK repeater build target name The actual target doesn't capitalize the 'r' in repeater. --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 732dac43f7..f212794173 100755 --- a/build.sh +++ b/build.sh @@ -15,8 +15,8 @@ Commands: build-room-server-firmwares: Build all chat room server firmwares for all build targets. Examples: -Build firmware for the "RAK_4631_Repeater" device target -$ sh build.sh build-firmware RAK_4631_Repeater +Build firmware for the "RAK_4631_repeater" device target +$ sh build.sh build-firmware RAK_4631_repeater Build all firmwares for device targets containing the string "RAK_4631" $ sh build.sh build-matching-firmwares From 03fc94901477b4c7fc15fa6757d1a1940d7eaa34 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 3 Nov 2025 14:23:32 +1100 Subject: [PATCH 075/409] * setting up framework for Regions, TransportKeys, etc --- examples/simple_repeater/MyMesh.cpp | 22 +++++++++++++-- examples/simple_repeater/MyMesh.h | 5 ++++ src/helpers/RegionMap.cpp | 35 +++++++++++++++++++++++ src/helpers/RegionMap.h | 39 +++++++++++++++++++++++++ src/helpers/TransportKeyStore.cpp | 44 +++++++++++++++++++++++++++++ src/helpers/TransportKeyStore.h | 23 +++++++++++++++ 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/helpers/RegionMap.cpp create mode 100644 src/helpers/RegionMap.h create mode 100644 src/helpers/TransportKeyStore.cpp create mode 100644 src/helpers/TransportKeyStore.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 1afad18b95..21f5a76b7b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -404,6 +404,23 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { return getRNG()->nextInt(0, 5*t + 1); } +mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) { + if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { + auto region = region_map.findMatch(pkt, REGION_ALLOW_FLOOD); + if (region == NULL) { + MESH_DEBUG_PRINTLN("onRecvPacket: unknown transport code for FLOOD packet"); + return ACTION_RELEASE; + } + } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { + if ((region_map.getWildcard().flags & REGION_ALLOW_FLOOD) == 0) { + MESH_DEBUG_PRINTLN("onRecvPacket: wildcard FLOOD packet not allowed"); + return ACTION_RELEASE; + } + } + // otherwise do normal processing + return mesh::Mesh::onRecvPacket(pkt); +} + void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, uint8_t *data, size_t len) { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin @@ -593,7 +610,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store) #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif @@ -652,8 +669,9 @@ void MyMesh::begin(FILESYSTEM *fs) { _fs = fs; // load persisted prefs _cli.loadPrefs(_fs); - acl.load(_fs); + // TODO: key_store.begin(); + region_map.load(_fs); #if defined(WITH_BRIDGE) if (_prefs.bridge_enabled) { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 694e8ff9ee..7e34ef5f9d 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef WITH_BRIDGE extern AbstractBridge* bridge; @@ -87,6 +88,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; ClientACL acl; + TransportKeyStore key_store; + RegionMap region_map; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS NeighbourInfo neighbours[MAX_NEIGHBOURS]; @@ -144,6 +147,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } #endif + mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp new file mode 100644 index 0000000000..4c270ff8b9 --- /dev/null +++ b/src/helpers/RegionMap.cpp @@ -0,0 +1,35 @@ +#include "RegionMap.h" +#include + +void RegionMap::load(FILESYSTEM* _fs) { + // TODO +} +void RegionMap::save(FILESYSTEM* _fs) { + // TODO +} + +RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if (region->flags & mask) { // does region allow this? (per 'mask' param) + TransportKey keys[4]; + int num = _store->loadKeysFor(region->name, region->id, keys, 4); + for (int j = 0; j < num; j++) { + uint16_t code = keys[j].calcTransportCode(packet); + if (packet->transport_codes[0] == code) { // a match!! + return region; + } + } + } + } + return NULL; // no matches +} + +const RegionEntry* RegionMap::findName(const char* name) const { + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if (strcmp(name, region->name) == 0) return region; + } + return NULL; // not found +} + diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h new file mode 100644 index 0000000000..4620a74517 --- /dev/null +++ b/src/helpers/RegionMap.h @@ -0,0 +1,39 @@ +#pragma once + +#include // needed for PlatformIO +#include +#include "TransportKeyStore.h" + +#ifndef MAX_REGION_ENTRIES + #define MAX_REGION_ENTRIES 32 +#endif + +#define REGION_ALLOW_FLOOD 0x01 + +struct RegionEntry { + uint16_t id; + uint16_t parent; + uint8_t flags; + char name[31]; +}; + +class RegionMap { + TransportKeyStore* _store; + uint16_t next_id; + uint16_t num_regions; + RegionEntry regions[MAX_REGION_ENTRIES]; + RegionEntry wildcard; + +public: + RegionMap(TransportKeyStore& store) : _store(&store) { + next_id = 1; num_regions = 0; + wildcard.id = wildcard.parent = 0; + wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood + } + void load(FILESYSTEM* _fs); + void save(FILESYSTEM* _fs); + + RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); + const RegionEntry& getWildcard() const { return wildcard; } + const RegionEntry* findName(const char* name) const; +}; diff --git a/src/helpers/TransportKeyStore.cpp b/src/helpers/TransportKeyStore.cpp new file mode 100644 index 0000000000..973ad703d8 --- /dev/null +++ b/src/helpers/TransportKeyStore.cpp @@ -0,0 +1,44 @@ +#include "TransportKeyStore.h" +#include + +uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const { + uint16_t code; + SHA256 sha; + sha.resetHMAC(key, sizeof(key)); + uint8_t type = packet->getPayloadType(); + sha.update(&type, 1); + sha.update(packet->payload, packet->payload_len); + sha.finalizeHMAC(key, sizeof(key), &code, 2); + return code; +} + +int TransportKeyStore::loadKeysFor(const char* name, uint16_t id, TransportKey keys[], int max_num) { + int n = 0; + for (int i = 0; i < num_cache && n < max_num; i++) { // first, check cache + if (cache_ids[i] == id) { + keys[n++] = cache_keys[i]; + } + } + if (n > 0) return n; // cache hit! + + if (*name == '#') { // is a publicly-known hashtag region + SHA256 sha; + sha.update(name, strlen(name)); + sha.finalize(&keys[0], sizeof(keys[0].key)); + n = 1; + } else { + // TODO: retrieve from difficult-to-copy keystore + } + + // store in cache (if room) + for (int i = 0; i < n; i++) { + if (num_cache < MAX_TKS_ENTRIES) { + cache_ids[num_cache] = id; + cache_keys[num_cache] = keys[i]; + num_cache++; + } else { + // TODO: evict oldest cache entry + } + } + return n; +} diff --git a/src/helpers/TransportKeyStore.h b/src/helpers/TransportKeyStore.h new file mode 100644 index 0000000000..f25ed53f12 --- /dev/null +++ b/src/helpers/TransportKeyStore.h @@ -0,0 +1,23 @@ +#pragma once + +#include // needed for PlatformIO +#include +#include + +struct TransportKey { + uint8_t key[16]; + + uint16_t calcTransportCode(const mesh::Packet* packet) const; +}; + +#define MAX_TKS_ENTRIES 16 + +class TransportKeyStore { + uint16_t cache_ids[MAX_TKS_ENTRIES]; + TransportKey cache_keys[MAX_TKS_ENTRIES]; + int num_cache; + +public: + TransportKeyStore() { num_cache = 0; } + int loadKeysFor(const char* name, uint16_t id, TransportKey keys[], int max_num); +}; From f797744f7c4148e1f77888dbb121451c6745f0c0 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 3 Nov 2025 18:14:44 +1100 Subject: [PATCH 076/409] * misc RegionMap and key store methods --- src/helpers/RegionMap.cpp | 67 ++++++++++++++++++++++++++++-- src/helpers/RegionMap.h | 8 +++- src/helpers/TransportKeyStore.cpp | 68 +++++++++++++++++++++++-------- src/helpers/TransportKeyStore.h | 9 +++- 4 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 4c270ff8b9..db9ea2d54a 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -1,4 +1,5 @@ #include "RegionMap.h" +#include #include void RegionMap::load(FILESYSTEM* _fs) { @@ -8,12 +9,36 @@ void RegionMap::save(FILESYSTEM* _fs) { // TODO } +RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id) { + auto region = findByName(name); + if (region) { + if (region->id == parent_id) return NULL; // ERROR: invalid parent! + + region->parent = parent_id; // re-parent / move this region in the hierarchy + } else { + if (num_regions >= MAX_REGION_ENTRIES) return NULL; // full! + + region = ®ions[num_regions++]; // alloc new RegionEntry + region->flags = 0; + region->id = next_id++; + StrHelper::strncpy(region->name, name, sizeof(region->name)); + region->parent = parent_id; + } + return region; +} + RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if (region->flags & mask) { // does region allow this? (per 'mask' param) + if ((region->flags & mask) == mask) { // does region allow this? (per 'mask' param) TransportKey keys[4]; - int num = _store->loadKeysFor(region->name, region->id, keys, 4); + int num; + if (region->name[0] == '#') { // auto hashtag region + _store->getAutoKeyFor(region->id, region->name, keys[0]); + num = 1; + } else { + num = _store->loadKeysFor(region->id, keys, 4); + } for (int j = 0; j < num; j++) { uint16_t code = keys[j].calcTransportCode(packet); if (packet->transport_codes[0] == code) { // a match!! @@ -25,7 +50,7 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { return NULL; // no matches } -const RegionEntry* RegionMap::findName(const char* name) const { +RegionEntry* RegionMap::findByName(const char* name) { for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; if (strcmp(name, region->name) == 0) return region; @@ -33,3 +58,39 @@ const RegionEntry* RegionMap::findName(const char* name) const { return NULL; // not found } +RegionEntry* RegionMap::findById(uint16_t id) { + if (id == 0) return &wildcard; // special root Region + + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if (region->id == id) return region; + } + return NULL; // not found +} + +bool RegionMap::removeRegion(const RegionEntry& region) { + if (region.id == 0) return false; // failed (cannot remove the wildcard Region) + + int i; // first check region has no child regions + for (i = 0; i < num_regions; i++) { + if (regions[i].parent == region.id) return false; // failed (must remove child Regions first) + } + + i = 0; + while (i < num_regions) { + if (region.id == regions[i].id) break; + i++; + } + if (i >= num_regions) return false; // failed (not found) + + num_regions--; // remove from regions array + while (i + 1 < num_regions) { + regions[i] = regions[i + 1]; + } + return true; // success +} + +bool RegionMap::clear() { + num_regions = 0; + return true; // success +} diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 4620a74517..69c5220ba2 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -33,7 +33,11 @@ class RegionMap { void load(FILESYSTEM* _fs); void save(FILESYSTEM* _fs); + RegionEntry* putRegion(const char* name, uint16_t parent_id); RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); - const RegionEntry& getWildcard() const { return wildcard; } - const RegionEntry* findName(const char* name) const; + RegionEntry& getWildcard() { return wildcard; } + RegionEntry* findByName(const char* name); + RegionEntry* findById(uint16_t id); + bool removeRegion(const RegionEntry& region); + bool clear(); }; diff --git a/src/helpers/TransportKeyStore.cpp b/src/helpers/TransportKeyStore.cpp index 973ad703d8..4f689a123b 100644 --- a/src/helpers/TransportKeyStore.cpp +++ b/src/helpers/TransportKeyStore.cpp @@ -12,7 +12,32 @@ uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const { return code; } -int TransportKeyStore::loadKeysFor(const char* name, uint16_t id, TransportKey keys[], int max_num) { +void TransportKeyStore::putCache(uint16_t id, const TransportKey& key) { + if (num_cache < MAX_TKS_ENTRIES) { + cache_ids[num_cache] = id; + cache_keys[num_cache] = key; + num_cache++; + } else { + // TODO: evict oldest cache entry + } +} + +void TransportKeyStore::getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest) { + for (int i = 0; i < num_cache; i++) { // first, check cache + if (cache_ids[i] == id) { // cache hit! + dest = cache_keys[i]; + return; + } + } + // calc key for publicly-known hashtag region name + SHA256 sha; + sha.update(name, strlen(name)); + sha.finalize(&dest.key, sizeof(dest.key)); + + putCache(id, dest); +} + +int TransportKeyStore::loadKeysFor(uint16_t id, TransportKey keys[], int max_num) { int n = 0; for (int i = 0; i < num_cache && n < max_num; i++) { // first, check cache if (cache_ids[i] == id) { @@ -21,24 +46,35 @@ int TransportKeyStore::loadKeysFor(const char* name, uint16_t id, TransportKey k } if (n > 0) return n; // cache hit! - if (*name == '#') { // is a publicly-known hashtag region - SHA256 sha; - sha.update(name, strlen(name)); - sha.finalize(&keys[0], sizeof(keys[0].key)); - n = 1; - } else { - // TODO: retrieve from difficult-to-copy keystore - } + // TODO: retrieve from difficult-to-copy keystore // store in cache (if room) for (int i = 0; i < n; i++) { - if (num_cache < MAX_TKS_ENTRIES) { - cache_ids[num_cache] = id; - cache_keys[num_cache] = keys[i]; - num_cache++; - } else { - // TODO: evict oldest cache entry - } + putCache(id, keys[i]); } return n; } + +bool TransportKeyStore::saveKeysFor(uint16_t id, const TransportKey keys[], int num) { + invalidateCache(); + + // TODO: update hardware keystore + + return false; // failed +} + +bool TransportKeyStore::removeKeys(uint16_t id) { + invalidateCache(); + + // TODO: remove from hardware keystore + + return false; // failed +} + +bool TransportKeyStore::clear() { + invalidateCache(); + + // TODO: clear hardware keystore + + return false; // failed +} diff --git a/src/helpers/TransportKeyStore.h b/src/helpers/TransportKeyStore.h index f25ed53f12..fc9d2532f8 100644 --- a/src/helpers/TransportKeyStore.h +++ b/src/helpers/TransportKeyStore.h @@ -17,7 +17,14 @@ class TransportKeyStore { TransportKey cache_keys[MAX_TKS_ENTRIES]; int num_cache; + void putCache(uint16_t id, const TransportKey& key); + void invalidateCache() { num_cache = 0; } + public: TransportKeyStore() { num_cache = 0; } - int loadKeysFor(const char* name, uint16_t id, TransportKey keys[], int max_num); + void getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest); + int loadKeysFor(uint16_t id, TransportKey keys[], int max_num); + bool saveKeysFor(uint16_t id, const TransportKey keys[], int num); + bool removeKeys(uint16_t id); + bool clear(); }; From ecd30f4d364d190beac467224063d56ed681c5f2 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 3 Nov 2025 22:53:14 +1100 Subject: [PATCH 077/409] * new CLI commands: region, region load, region save, region get, region allow --- examples/simple_repeater/MyMesh.cpp | 83 +++++++++++++++++- examples/simple_repeater/MyMesh.h | 4 +- examples/simple_repeater/main.cpp | 4 +- src/helpers/RegionMap.cpp | 126 ++++++++++++++++++++++++++-- src/helpers/RegionMap.h | 22 +++-- 5 files changed, 218 insertions(+), 21 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 21f5a76b7b..2cd81ef675 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -610,7 +610,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store) #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif @@ -624,6 +624,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc dirty_contacts_expiry = 0; set_radio_at = revert_radio_at = 0; _logging = false; + region_load_active = false; #if MAX_NEIGHBOURS memset(neighbours, 0, sizeof(neighbours)); @@ -846,9 +847,46 @@ void MyMesh::clearStats() { ((SimpleMeshTables *)getTables())->resetStats(); } +static bool is_name_char(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= 'z') || c == '-' || c == '.' || c == '_' || c == '#'; +} + void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { - while (*command == ' ') - command++; // skip leading spaces + if (region_load_active) { + if (*command == 0) { // empty line, signal to terminate 'load' operation + region_map = temp_map; // copy over the temp instance as new current map + region_load_active = false; + + sprintf(reply, "OK - loaded %d regions", region_map.getCount()); + } else { + char *np = command; + while (*np == ' ') np++; // skip indent + int indent = np - command; + + char *ep = np; + while (is_name_char(*ep)) ep++; + if (*ep) { *ep++ = 0; } // set null terminator for end of name + + while (*ep && *ep != 'F') ep++; // look for (optional flags) + + if (indent > 0 && indent < 8) { + auto parent = load_stack[indent - 1]; + if (parent) { + auto old = region_map.findByName(np); + auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists) + if (nw) { + nw->flags = old ? old->flags : (*ep == 'F' ? REGION_ALLOW_FLOOD : 0); // carry-over flags from curr + + load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's + } + } + } + reply[0] = 0; + } + return; + } + + while (*command == ' ') command++; // skip leading spaces if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) memcpy(reply, command, 3); // reflect the prefix back @@ -890,6 +928,45 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply Serial.printf("\n"); } reply[0] = 0; + } else if (memcmp(command, "region", 6) == 0) { + reply[0] = 0; + + const char* parts[4]; + int n = mesh::Utils::parseTextParts(command, parts, 4, ' '); + if (n == 1 && sender_timestamp == 0) { + region_map.exportTo(Serial); + } else if (n >= 2 && strcmp(parts[1], "load") == 0) { + temp_map.resetFrom(region_map); // rebuild regions in a temp instance + memset(load_stack, 0, sizeof(load_stack)); + load_stack[0] = &temp_map.getWildcard(); + region_load_active = true; + } else if (n >= 2 && strcmp(parts[1], "save") == 0) { + bool success = region_map.save(_fs); + strcpy(reply, success ? "OK" : "Err - save failed"); + } else if (n >= 3 && strcmp(parts[1], "allow") == 0) { + auto region = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard(); + if (region) { + strcpy(reply, "OK"); + if (strcmp(parts[2], "F") == 0) { + region->flags = REGION_ALLOW_FLOOD; + } else if (strcmp(parts[2], "0") == 0) { + region->flags = 0; + } else { + sprintf(reply, "Err - invalid flag: %s", parts[2]); + } + } else { + strcpy(reply, "Err - unknown region"); + } + } else if (n >= 3 && strcmp(parts[1], "get") == 0) { + auto region = region_map.findByNamePrefix(parts[2]); + if (region) { + sprintf(reply, " %s %s", region->name, region->flags == REGION_ALLOW_FLOOD ? "F" : ""); + } else { + strcpy(reply, "Err - unknown region"); + } + } else { + strcpy(reply, "Err - ??"); + } } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 7e34ef5f9d..9ab937474a 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -89,7 +89,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t reply_data[MAX_PACKET_PAYLOAD]; ClientACL acl; TransportKeyStore key_store; - RegionMap region_map; + RegionMap region_map, temp_map; + RegionEntry* load_stack[8]; + bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS NeighbourInfo neighbours[MAX_NEIGHBOURS]; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 5843df748c..7387e77e73 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -91,14 +91,16 @@ void loop() { if (c != '\n') { command[len++] = c; command[len] = 0; + Serial.print(c); } - Serial.print(c); + if (c == '\r') break; } if (len == sizeof(command)-1) { // command buffer full command[sizeof(command)-1] = '\r'; } if (len > 0 && command[len - 1] == '\r') { // received complete line + Serial.print('\n'); command[len - 1] = 0; // replace newline with C string null terminator char reply[160]; the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index db9ea2d54a..7b6456b4ad 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -2,25 +2,106 @@ #include #include -void RegionMap::load(FILESYSTEM* _fs) { - // TODO +RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { + next_id = 1; num_regions = 0; + wildcard.id = wildcard.parent = 0; + wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood + strcpy(wildcard.name, "(*)"); } -void RegionMap::save(FILESYSTEM* _fs) { - // TODO + +static File openWrite(FILESYSTEM* _fs, const char* filename) { + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(filename); + return _fs->open(filename, FILE_O_WRITE); + #elif defined(RP2040_PLATFORM) + return _fs->open(filename, "w"); + #else + return _fs->open(filename, "w", true); + #endif +} + +bool RegionMap::load(FILESYSTEM* _fs) { + if (_fs->exists("/regions2")) { + #if defined(RP2040_PLATFORM) + File file = _fs->open("/regions2", "r"); + #else + File file = _fs->open("/regions2"); + #endif + + if (file) { + uint8_t pad[128]; + + num_regions = 0; next_id = 1; + + bool success = file.read(pad, 7) == 7; // reserved header + success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags); + success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id); + + if (success) { + while (num_regions < MAX_REGION_ENTRIES) { + auto r = ®ions[num_regions]; + + success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id); + success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent); + success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name); + success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags); + success = success && file.read(pad, sizeof(pad)) == sizeof(pad); + + if (!success) break; // EOF + + if (r->id >= next_id) { // make sure next_id is valid + next_id = r->id + 1; + } + num_regions++; + } + } + file.close(); + return true; + } + } + return false; // failed +} + +bool RegionMap::save(FILESYSTEM* _fs) { + File file = openWrite(_fs, "/regions2"); + if (file) { + uint8_t pad[128]; + memset(pad, 0, sizeof(pad)); + + bool success = file.write(pad, 7) == 7; // reserved header + success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags); + success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id); + + if (success) { + for (int i = 0; i < num_regions; i++) { + auto r = ®ions[i]; + + success = file.write((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id); + success = success && file.write((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent); + success = success && file.write((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name); + success = success && file.write((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags); + success = success && file.write(pad, sizeof(pad)) == sizeof(pad); + if (!success) break; // write failed + } + } + file.close(); + return true; + } + return false; // failed } -RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id) { +RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) { auto region = findByName(name); if (region) { if (region->id == parent_id) return NULL; // ERROR: invalid parent! region->parent = parent_id; // re-parent / move this region in the hierarchy } else { - if (num_regions >= MAX_REGION_ENTRIES) return NULL; // full! + if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL; // full! region = ®ions[num_regions++]; // alloc new RegionEntry region->flags = 0; - region->id = next_id++; + region->id = id == 0 ? next_id++ : id; StrHelper::strncpy(region->name, name, sizeof(region->name)); region->parent = parent_id; } @@ -58,6 +139,14 @@ RegionEntry* RegionMap::findByName(const char* name) { return NULL; // not found } +RegionEntry* RegionMap::findByNamePrefix(const char* prefix) { + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if (memcmp(prefix, region->name, strlen(prefix)) == 0) return region; + } + return NULL; // not found +} + RegionEntry* RegionMap::findById(uint16_t id) { if (id == 0) return &wildcard; // special root Region @@ -94,3 +183,26 @@ bool RegionMap::clear() { num_regions = 0; return true; // success } + +void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out) const { + for (int i = 0; i < indent; i++) { + out.print(' '); + } + + if (parent->flags & REGION_ALLOW_FLOOD) { + out.printf("%s F\n", parent->name); + } else { + out.printf("%s\n", parent->name); + } + + for (int i = 0; i < num_regions; i++) { + auto r = ®ions[i]; + if (r->parent == parent->id) { + printChildRegions(indent + 1, r, out); + } + } +} + +void RegionMap::exportTo(Stream& out) const { + printChildRegions(0, &wildcard, out); // recursive +} diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 69c5220ba2..3858bfbde4 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -24,20 +24,24 @@ class RegionMap { RegionEntry regions[MAX_REGION_ENTRIES]; RegionEntry wildcard; + void printChildRegions(int indent, const RegionEntry* parent, Stream& out) const; + public: - RegionMap(TransportKeyStore& store) : _store(&store) { - next_id = 1; num_regions = 0; - wildcard.id = wildcard.parent = 0; - wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood - } - void load(FILESYSTEM* _fs); - void save(FILESYSTEM* _fs); - - RegionEntry* putRegion(const char* name, uint16_t parent_id); + RegionMap(TransportKeyStore& store); + + bool load(FILESYSTEM* _fs); + bool save(FILESYSTEM* _fs); + + RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0); RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); RegionEntry& getWildcard() { return wildcard; } RegionEntry* findByName(const char* name); + RegionEntry* findByNamePrefix(const char* prefix); RegionEntry* findById(uint16_t id); bool removeRegion(const RegionEntry& region); bool clear(); + void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; } + int getCount() const { return num_regions; } + + void exportTo(Stream& out) const; }; From d9ff3a4d02d25005eac27c40bd58b6c43f29c9ed Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 4 Nov 2025 01:21:56 +1100 Subject: [PATCH 078/409] * Mesh: new sendFlood() overload with transport codes. * BaseChatMesh: sendFloodScoped(), for overriding with some outbound 'scope' / TransportKey * companion: new 'send_scope' variable. --- examples/companion_radio/MyMesh.cpp | 24 ++++++++++++++++++++++ examples/companion_radio/MyMesh.h | 6 ++++++ src/Mesh.cpp | 25 +++++++++++++++++++++++ src/Mesh.h | 6 ++++++ src/helpers/BaseChatMesh.cpp | 31 ++++++++++++++++++----------- src/helpers/BaseChatMesh.h | 3 +++ src/helpers/TransportKeyStore.cpp | 7 +++++++ src/helpers/TransportKeyStore.h | 1 + 8 files changed, 91 insertions(+), 12 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 02f1a21ded..b8ddbfa730 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -378,6 +378,29 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe #endif } +void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { + // TODO: dynamic send_scope, depending on recipient and current 'home' Region + if (send_scope.isNull()) { + sendFlood(pkt, delay_millis); + } else { + uint16_t codes[2]; + codes[0] = send_scope.calcTransportCode(pkt); + codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region? + sendFlood(pkt, codes, delay_millis); + } +} +void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) { + // TODO: have per-channel send_scope + if (send_scope.isNull()) { + sendFlood(pkt, delay_millis); + } else { + uint16_t codes[2]; + codes[0] = send_scope.calcTransportCode(pkt); + codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region? + sendFlood(pkt, codes, delay_millis); + } +} + void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp, const char *text) { markConnectionActive(from); // in case this is from a server, and we have a connection @@ -663,6 +686,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe sign_data = NULL; dirty_contacts_expiry = 0; memset(advert_paths, 0, sizeof(advert_paths)); + memset(send_scope.key, 0, sizeof(send_scope.key)); // defaults memset(&_prefs, 0, sizeof(_prefs)); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e640087105..bfb8bb1809 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -68,6 +68,7 @@ #endif #include +#include /* -------------------------------------------------------------------------------------- */ @@ -107,6 +108,9 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { int calcRxDelay(float score, uint32_t air_time) const override; uint8_t getExtraAckTransmitCount() const override; + void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override; + void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override; + void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; bool isAutoAddEnabled() const override; bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; @@ -191,6 +195,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { uint32_t sign_data_len; unsigned long dirty_contacts_expiry; + TransportKey send_scope; + uint8_t cmd_frame[MAX_FRAME_SIZE + 1]; uint8_t out_frame[MAX_FRAME_SIZE + 1]; CayenneLPP telemetry; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 53dc74f5d5..1ee6ce5c80 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -610,6 +610,31 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) { sendPacket(packet, pri, delay_millis); } +void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) { + if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { + MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); + return; + } + + packet->header &= ~PH_ROUTE_MASK; + packet->header |= ROUTE_TYPE_TRANSPORT_FLOOD; + packet->transport_codes[0] = transport_codes[0]; + packet->transport_codes[1] = transport_codes[1]; + packet->path_len = 0; + + _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + + uint8_t pri; + if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { + pri = 2; + } else if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT) { + pri = 3; // de-prioritie these + } else { + pri = 1; + } + sendPacket(packet, pri, delay_millis); +} + void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis) { packet->header &= ~PH_ROUTE_MASK; packet->header |= ROUTE_TYPE_DIRECT; diff --git a/src/Mesh.h b/src/Mesh.h index cbf1c9cf07..e96043e8aa 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -186,6 +186,12 @@ class Mesh : public Dispatcher { */ void sendFlood(Packet* packet, uint32_t delay_millis=0); + /** + * \brief send a locally-generated Packet with flood routing + * \param transport_codes array of 2 codes to attach to packet + */ + void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); + /** * \brief send a locally-generated Packet with Direct routing */ diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 9b1eb1ce78..b4072657b2 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -9,6 +9,13 @@ #define TXT_ACK_DELAY 200 #endif +void BaseChatMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { + sendFlood(pkt, delay_millis); +} +void BaseChatMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) { + sendFlood(pkt, delay_millis); +} + mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; @@ -34,7 +41,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { if (dest.out_path_len < 0) { mesh::Packet* ack = createAck(ack_hash); - if (ack) sendFlood(ack, TXT_ACK_DELAY); + if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY); } else { uint32_t d = TXT_ACK_DELAY; if (getExtraAckTransmitCount() > 0) { @@ -175,7 +182,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); - if (path) sendFlood(path, TXT_ACK_DELAY); + if (path) sendFloodScoped(from, path, TXT_ACK_DELAY); } else { sendAckTo(from, ack_hash); } @@ -186,7 +193,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra) mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0); - if (path) sendFlood(path); + if (path) sendFloodScoped(from, path); } } else if (flags == TXT_TYPE_SIGNED_PLAIN) { if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date @@ -202,7 +209,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); - if (path) sendFlood(path, TXT_ACK_DELAY); + if (path) sendFloodScoped(from, path, TXT_ACK_DELAY); } else { sendAckTo(from, ack_hash); } @@ -218,14 +225,14 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len); if (reply) { if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY); } } } @@ -346,7 +353,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, int rc; if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; } else { @@ -372,7 +379,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); int rc; if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; } else { @@ -398,7 +405,7 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len); if (pkt) { - sendFlood(pkt); + sendFloodScoped(channel, pkt); return true; } return false; @@ -460,7 +467,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; } else { @@ -487,7 +494,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; } else { @@ -514,7 +521,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; } else { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 9392001ee2..76b0dd1cc2 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -107,6 +107,9 @@ class BaseChatMesh : public mesh::Mesh { virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0; virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len); + virtual void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0); + virtual void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0); + // storage concepts, for sub-classes to override/implement virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented virtual bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { return false; } diff --git a/src/helpers/TransportKeyStore.cpp b/src/helpers/TransportKeyStore.cpp index 4f689a123b..8b7c891bc5 100644 --- a/src/helpers/TransportKeyStore.cpp +++ b/src/helpers/TransportKeyStore.cpp @@ -12,6 +12,13 @@ uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const { return code; } +bool TransportKey::isNull() const { + for (int i = 0; i < sizeof(key); i++) { + if (key[i]) return false; + } + return true; // key is all zeroes +} + void TransportKeyStore::putCache(uint16_t id, const TransportKey& key) { if (num_cache < MAX_TKS_ENTRIES) { cache_ids[num_cache] = id; diff --git a/src/helpers/TransportKeyStore.h b/src/helpers/TransportKeyStore.h index fc9d2532f8..e3ba152434 100644 --- a/src/helpers/TransportKeyStore.h +++ b/src/helpers/TransportKeyStore.h @@ -8,6 +8,7 @@ struct TransportKey { uint8_t key[16]; uint16_t calcTransportCode(const mesh::Packet* packet) const; + bool isNull() const; }; #define MAX_TKS_ENTRIES 16 From 397d280c3bdf524134726f644111108a3bc1f5f9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:25:31 +0100 Subject: [PATCH 079/409] stop OLED powering on every message if connected to phone --- examples/companion_radio/ui-new/UITask.cpp | 9 ++++++++- examples/companion_radio/ui-orig/UITask.cpp | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a08928..886823ff83 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -596,9 +596,16 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i setCurrScreen(msg_preview); if (_display != NULL) { - if (!_display->isOn()) _display->turnOn(); + // Only turn on display if it's off AND not connected to phone via BLE + // If connected to phone, user will see messages there, so don't wake the OLED + if (!_display->isOn() && !hasConnection()) { + _display->turnOn(); + } + // Always extend auto-off timer and trigger refresh if display is on + if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _next_refresh = 100; // trigger refresh + } } } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d37..20d45bec71 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -136,9 +136,16 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i StrHelper::strncpy(_msg, text, sizeof(_msg)); if (_display != NULL) { - if (!_display->isOn()) _display->turnOn(); + // Only turn on display if it's off AND not connected to phone via BLE + // If connected to phone, user will see messages there, so don't wake the OLED + if (!_display->isOn() && !hasConnection()) { + _display->turnOn(); + } + // Always extend auto-off timer and trigger refresh if display is on + if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _need_refresh = true; + } } } From eae16cfc5f0690cb20bcce92918c04f39d348e9c Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:39:35 +0100 Subject: [PATCH 080/409] less unnecessary comments, less lines of code :) --- examples/companion_radio/ui-new/UITask.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 886823ff83..27734135d8 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -596,12 +596,9 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i setCurrScreen(msg_preview); if (_display != NULL) { - // Only turn on display if it's off AND not connected to phone via BLE - // If connected to phone, user will see messages there, so don't wake the OLED if (!_display->isOn() && !hasConnection()) { _display->turnOn(); } - // Always extend auto-off timer and trigger refresh if display is on if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _next_refresh = 100; // trigger refresh From 99a34731693432e844db03622ded3b739de705dc Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:41:11 +0100 Subject: [PATCH 081/409] even less comments \o/ --- examples/companion_radio/ui-orig/UITask.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 20d45bec71..3b36e45dcf 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -136,12 +136,9 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i StrHelper::strncpy(_msg, text, sizeof(_msg)); if (_display != NULL) { - // Only turn on display if it's off AND not connected to phone via BLE - // If connected to phone, user will see messages there, so don't wake the OLED if (!_display->isOn() && !hasConnection()) { _display->turnOn(); } - // Always extend auto-off timer and trigger refresh if display is on if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _need_refresh = true; From 5c80334dbda5a09fd44104a70168fe79a1bf0fea Mon Sep 17 00:00:00 2001 From: David Hall Date: Mon, 3 Nov 2025 21:00:43 +0000 Subject: [PATCH 082/409] Fix manufacturer name on Seeed Xiao S3 WIO --- variants/xiao_s3_wio/XiaoS3WIOBoard.h | 13 +++++++++++++ variants/xiao_s3_wio/target.cpp | 3 ++- variants/xiao_s3_wio/target.h | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 variants/xiao_s3_wio/XiaoS3WIOBoard.h diff --git a/variants/xiao_s3_wio/XiaoS3WIOBoard.h b/variants/xiao_s3_wio/XiaoS3WIOBoard.h new file mode 100644 index 0000000000..7ae06a359b --- /dev/null +++ b/variants/xiao_s3_wio/XiaoS3WIOBoard.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +class XiaoS3WIOBoard : public ESP32Board { +public: + XiaoS3WIOBoard() { } + + const char* getManufacturerName() const override { + return "Xiao S3 WIO"; + } +}; diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index ed8584ff04..f7de924416 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -1,7 +1,8 @@ #include #include "target.h" +#include "XiaoS3WIOBoard.h" -ESP32Board board; +XiaoS3WIOBoard board; #if defined(P_LORA_SCLK) static SPIClass spi; diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index f184c7575a..b84ab42b09 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -11,8 +11,9 @@ #include #include #endif +#include "XiaoS3WIOBoard.h" -extern ESP32Board board; +extern XiaoS3WIOBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; From c3dbec41bac0730b778efd8202cd2d29466c0b9b Mon Sep 17 00:00:00 2001 From: David Hall Date: Mon, 3 Nov 2025 21:02:08 +0000 Subject: [PATCH 083/409] Fix manufacturer name on Seeed Xiao S3 WIO --- variants/xiao_s3_wio/target.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index f7de924416..41f25da6a1 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -1,6 +1,5 @@ #include #include "target.h" -#include "XiaoS3WIOBoard.h" XiaoS3WIOBoard board; From 04c0c40b391e5e9d78ce83dba5e539b30e4c1443 Mon Sep 17 00:00:00 2001 From: recrof Date: Tue, 4 Nov 2025 23:58:32 +0100 Subject: [PATCH 084/409] set max contacts to 350 and channels to 40 for esp32c3, s3 and c6 --- variants/ebyte_eora_s3/platformio.ini | 10 ++++---- variants/generic_espnow/platformio.ini | 4 ++-- variants/heltec_ct62/platformio.ini | 8 +++---- variants/heltec_e213/platformio.ini | 8 +++---- variants/heltec_e290/platformio.ini | 8 +++---- variants/heltec_t190/platformio.ini | 8 +++---- variants/heltec_tracker/platformio.ini | 4 ++-- variants/heltec_tracker_v2/platformio.ini | 14 +++++------ variants/heltec_v3/platformio.ini | 24 +++++++++---------- variants/heltec_v4/platformio.ini | 14 +++++------ variants/heltec_wireless_paper/platformio.ini | 4 ++-- variants/lilygo_t3s3/platformio.ini | 10 ++++---- variants/lilygo_t3s3_sx1276/platformio.ini | 10 ++++---- .../platformio.ini | 4 ++-- variants/lilygo_tlora_c6/platformio.ini | 4 ++-- variants/meshadventurer/platformio.ini | 8 +++---- .../sensecap_indicator-espnow/platformio.ini | 4 ++-- variants/station_g2/platformio.ini | 12 +++++----- variants/thinknode_m2/platformio.ini | 12 +++++----- variants/xiao_c3/platformio.ini | 12 +++++----- variants/xiao_c6/platformio.ini | 12 +++++----- variants/xiao_s3_wio/platformio.ini | 16 ++++++------- 22 files changed, 105 insertions(+), 105 deletions(-) diff --git a/variants/ebyte_eora_s3/platformio.ini b/variants/ebyte_eora_s3/platformio.ini index df622a3451..bdf6bba3d2 100644 --- a/variants/ebyte_eora_s3/platformio.ini +++ b/variants/ebyte_eora_s3/platformio.ini @@ -64,7 +64,7 @@ lib_deps = extends = Ebyte_EoRa-S3 build_flags = ${Ebyte_EoRa-S3.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -99,8 +99,8 @@ build_flags = ${Ebyte_EoRa-S3.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} @@ -118,8 +118,8 @@ build_flags = ${Ebyte_EoRa-S3.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/generic_espnow/platformio.ini b/variants/generic_espnow/platformio.ini index c40833dce9..cdeed076a4 100644 --- a/variants/generic_espnow/platformio.ini +++ b/variants/generic_espnow/platformio.ini @@ -26,7 +26,7 @@ build_src_filter = ${esp32_base.build_src_filter} extends = Generic_ESPNOW build_flags = ${Generic_ESPNOW.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 build_src_filter = ${Generic_ESPNOW.build_src_filter} +<../examples/simple_secure_chat/main.cpp> @@ -54,7 +54,7 @@ lib_deps = extends = Generic_ESPNOW build_flags = ${Generic_ESPNOW.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 1b6121e44b..1f2e330a37 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -97,8 +97,8 @@ build_flags = ${Heltec_ct62.build_flags} ; -D ARDUINO_USB_MODE=1 ; -D ARDUINO_USB_CDC_ON_BOOT=1 - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -115,8 +115,8 @@ build_flags = ${Heltec_ct62.build_flags} ; -D ARDUINO_USB_MODE=1 ; -D ARDUINO_USB_CDC_ON_BOOT=1 - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D BLE_PIN_CODE=123456 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 93bdc00034..caba3a30e0 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -45,8 +45,8 @@ extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E213Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 @@ -65,8 +65,8 @@ extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E213Display -D OFFLINE_QUEUE_SIZE=256 build_src_filter = ${Heltec_E213_base.build_src_filter} diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 201b3631d3..0c07c592c4 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -39,8 +39,8 @@ extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E290Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 @@ -59,8 +59,8 @@ extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E290Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 01e9dfc917..8d21c5231c 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -52,8 +52,8 @@ extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -70,8 +70,8 @@ extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 build_src_filter = ${Heltec_T190_base.build_src_filter} + diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 19ab49a848..4f48ac21ab 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -43,8 +43,8 @@ build_flags = -I examples/companion_radio/ui-new -D DISPLAY_ROTATION=1 -D DISPLAY_CLASS=ST7735Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 ; HWT will use display for pin -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index c4a79d9e47..61ccd4f4be 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -120,7 +120,7 @@ lib_deps = extends = Heltec_tracker_v2 build_flags = ${Heltec_tracker_v2.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -135,8 +135,8 @@ extends = Heltec_tracker_v2 build_flags = ${Heltec_tracker_v2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7735Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 @@ -154,8 +154,8 @@ extends = Heltec_tracker_v2 build_flags = ${Heltec_tracker_v2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7735Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D AUTO_SHUTDOWN_MILLIVOLTS=3400 @@ -179,8 +179,8 @@ extends = Heltec_tracker_v2 build_flags = ${Heltec_tracker_v2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7735Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index dcf566b32e..36c6386f67 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -125,7 +125,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -140,8 +140,8 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 @@ -159,8 +159,8 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D AUTO_SHUTDOWN_MILLIVOLTS=3400 @@ -183,8 +183,8 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' @@ -304,8 +304,8 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -323,7 +323,7 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -D MAX_CONTACTS=140 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -336,8 +336,8 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 2c06b97819..c26a5bc69d 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -112,7 +112,7 @@ lib_deps = extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -127,8 +127,8 @@ extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 @@ -146,8 +146,8 @@ extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D AUTO_SHUTDOWN_MILLIVOLTS=3400 @@ -170,8 +170,8 @@ extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 20bf4c64bf..c9ad758b13 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -38,8 +38,8 @@ extends = Heltec_Wireless_Paper_base build_flags = ${Heltec_Wireless_Paper_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E213Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index a97a692aea..0f01c9b764 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -102,7 +102,7 @@ lib_deps = extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -137,8 +137,8 @@ build_flags = ${LilyGo_T3S3_sx1262.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} @@ -156,8 +156,8 @@ build_flags = ${LilyGo_T3S3_sx1262.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index eee8bdbd5f..f544be113d 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -100,7 +100,7 @@ lib_deps = extends = LilyGo_T3S3_sx1276 build_flags = ${LilyGo_T3S3_sx1276.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -136,8 +136,8 @@ build_flags = ${LilyGo_T3S3_sx1276.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} @@ -155,8 +155,8 @@ build_flags = ${LilyGo_T3S3_sx1276.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index ef31e6e16b..2d2a095aab 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -116,8 +116,8 @@ extends = T_Beam_S3_Supreme_SX1262 build_flags = ${T_Beam_S3_Supreme_SX1262.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index 88a811b5a5..b29cd0360a 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -67,8 +67,8 @@ lib_deps = [env:LilyGo_Tlora_C6_companion_radio_ble_] extends = tlora_c6 build_flags = ${tlora_c6.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index c52f4b7bad..18b64ac329 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -190,7 +190,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -212,7 +212,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -275,7 +275,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1268Wrapper -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -297,7 +297,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1268Wrapper -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/sensecap_indicator-espnow/platformio.ini b/variants/sensecap_indicator-espnow/platformio.ini index 064f3ae880..e643d03398 100644 --- a/variants/sensecap_indicator-espnow/platformio.ini +++ b/variants/sensecap_indicator-espnow/platformio.ini @@ -37,8 +37,8 @@ extends =SenseCapIndicator-ESPNow build_flags = ${SenseCapIndicator-ESPNow.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 ; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index bda8b7aea5..9ee8f829c5 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -184,8 +184,8 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} @@ -201,8 +201,8 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -221,8 +221,8 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini index 0238947f96..fb691d920b 100644 --- a/variants/thinknode_m2/platformio.ini +++ b/variants/thinknode_m2/platformio.ini @@ -118,8 +118,8 @@ lib_deps = extends = ThinkNode_M2 build_flags = ${ThinkNode_M2.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M2.build_src_filter} @@ -133,8 +133,8 @@ extends = ThinkNode_M2 build_flags = ${ThinkNode_M2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 @@ -155,8 +155,8 @@ extends = ThinkNode_M2 build_flags = ${ThinkNode_M2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D SERIAL_TX=D6 -D SERIAL_RX=D7 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 683199d93a..76b72174af 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -73,8 +73,8 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} + build_flags = ${Xiao_esp32_C3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 @@ -92,8 +92,8 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} + build_flags = ${Xiao_esp32_C3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 @@ -110,8 +110,8 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} + build_flags = ${Xiao_esp32_C3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 9c9710835a..8f02dc8703 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -50,8 +50,8 @@ lib_deps = [env:Xiao_C6_companion_radio_ble_] extends = Xiao_C6 build_flags = ${Xiao_C6.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -107,8 +107,8 @@ lib_deps = [env:Meshimi_companion_radio_ble_] extends = Meshimi build_flags = ${Meshimi.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -167,8 +167,8 @@ lib_deps = [env:WHY2025_badge_companion_radio_ble_] extends = WHY2025_badge build_flags = ${WHY2025_badge.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index e5cc4c8cd8..95a54012db 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -107,8 +107,8 @@ lib_deps = extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_S3_WIO.build_src_filter} @@ -122,8 +122,8 @@ extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 @@ -144,8 +144,8 @@ extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D DISPLAY_CLASS=SSD1306Display -D OFFLINE_QUEUE_SIZE=256 @@ -168,8 +168,8 @@ extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D SERIAL_TX=D6 -D SERIAL_RX=D7 From 9ebeb477aa3313123323a967c8981a6ab96ea82f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 5 Nov 2025 14:34:44 +1100 Subject: [PATCH 085/409] * RegionMap: inverted 'flags' to _deny_ bits * Mesh: new filterRecvFloodPacket() for overriding * repeater CLI: 'allow' -> 'allowf' or 'denyf' --- examples/companion_radio/MyMesh.cpp | 6 +++++ examples/companion_radio/MyMesh.h | 1 + examples/simple_repeater/MyMesh.cpp | 38 +++++++++++++++-------------- examples/simple_repeater/MyMesh.h | 2 +- src/Mesh.cpp | 2 ++ src/Mesh.h | 5 ++++ src/helpers/RegionMap.cpp | 12 ++++----- src/helpers/RegionMap.h | 3 ++- 8 files changed, 43 insertions(+), 26 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index b8ddbfa730..3c882d189c 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -378,6 +378,12 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe #endif } +bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) { + // REVISIT: try to determine which Region (from transport_codes[1]) that Sender is indicating for replies/responses + // if unknown, fallback to finding Region from transport_codes[0], the 'scope' used by Sender + return false; +} + void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { // TODO: dynamic send_scope, depending on recipient and current 'home' Region if (send_scope.isNull()) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index bfb8bb1809..fb0fb479d9 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -107,6 +107,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { int getInterferenceThreshold() const override; int calcRxDelay(float score, uint32_t air_time) const override; uint8_t getExtraAckTransmitCount() const override; + bool filterRecvFloodPacket(mesh::Packet* packet) override; void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override; void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 2cd81ef675..dce0f18bf5 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -404,21 +404,21 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { return getRNG()->nextInt(0, 5*t + 1); } -mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) { +bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { - auto region = region_map.findMatch(pkt, REGION_ALLOW_FLOOD); + auto region = region_map.findMatch(pkt, REGION_DENY_FLOOD); if (region == NULL) { MESH_DEBUG_PRINTLN("onRecvPacket: unknown transport code for FLOOD packet"); - return ACTION_RELEASE; + return true; } } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { - if ((region_map.getWildcard().flags & REGION_ALLOW_FLOOD) == 0) { + if (region_map.getWildcard().flags & REGION_DENY_FLOOD) { MESH_DEBUG_PRINTLN("onRecvPacket: wildcard FLOOD packet not allowed"); - return ACTION_RELEASE; + return true; } } // otherwise do normal processing - return mesh::Mesh::onRecvPacket(pkt); + return false; } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, @@ -867,7 +867,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply while (is_name_char(*ep)) ep++; if (*ep) { *ep++ = 0; } // set null terminator for end of name - while (*ep && *ep != 'F') ep++; // look for (optional flags) + while (*ep && *ep != 'F') ep++; // look for (optional) flags if (indent > 0 && indent < 8) { auto parent = load_stack[indent - 1]; @@ -875,7 +875,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply auto old = region_map.findByName(np); auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists) if (nw) { - nw->flags = old ? old->flags : (*ep == 'F' ? REGION_ALLOW_FLOOD : 0); // carry-over flags from curr + nw->flags = old ? old->flags : (*ep == 'F' ? 0 : REGION_DENY_FLOOD); // carry-over flags from curr load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's } @@ -943,24 +943,26 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else if (n >= 2 && strcmp(parts[1], "save") == 0) { bool success = region_map.save(_fs); strcpy(reply, success ? "OK" : "Err - save failed"); - } else if (n >= 3 && strcmp(parts[1], "allow") == 0) { - auto region = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard(); + } else if (n >= 2 && strcmp(parts[1], "allowf") == 0) { + auto region = n >= 3 ? region_map.findByNamePrefix(parts[2]) : ®ion_map.getWildcard(); if (region) { + region->flags &= ~REGION_DENY_FLOOD; + strcpy(reply, "OK"); + } else { + strcpy(reply, "Err - unknown region"); + } + } else if (n >= 2 && strcmp(parts[1], "denyf") == 0) { + auto region = n >= 3 ? region_map.findByNamePrefix(parts[2]) : ®ion_map.getWildcard(); + if (region) { + region->flags |= REGION_DENY_FLOOD; strcpy(reply, "OK"); - if (strcmp(parts[2], "F") == 0) { - region->flags = REGION_ALLOW_FLOOD; - } else if (strcmp(parts[2], "0") == 0) { - region->flags = 0; - } else { - sprintf(reply, "Err - invalid flag: %s", parts[2]); - } } else { strcpy(reply, "Err - unknown region"); } } else if (n >= 3 && strcmp(parts[1], "get") == 0) { auto region = region_map.findByNamePrefix(parts[2]); if (region) { - sprintf(reply, " %s %s", region->name, region->flags == REGION_ALLOW_FLOOD ? "F" : ""); + sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F"); } else { strcpy(reply, "Err - unknown region"); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 9ab937474a..8333154176 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -149,7 +149,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } #endif - mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; + bool filterRecvFloodPacket(mesh::Packet* pkt) override; void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 1ee6ce5c80..71b8eaeeea 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -90,6 +90,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard. } + if (pkt->isRouteFlood() && filterRecvFloodPacket(pkt)) return ACTION_RELEASE; + DispatcherAction action = ACTION_RELEASE; switch (pkt->getPayloadType()) { diff --git a/src/Mesh.h b/src/Mesh.h index e96043e8aa..70fa80f379 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -43,6 +43,11 @@ class Mesh : public Dispatcher { */ DispatcherAction routeRecvPacket(Packet* packet); + /** + * \returns true, if given packet should be NOT be processed. + */ + virtual bool filterRecvFloodPacket(Packet* packet) { return false; } + /** * \brief Check whether this packet should be forwarded (re-transmitted) or not. * Is sub-classes responsibility to make sure given packet is only transmitted ONCE (by this node) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 7b6456b4ad..074084ec1f 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -5,7 +5,7 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { next_id = 1; num_regions = 0; wildcard.id = wildcard.parent = 0; - wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood + wildcard.flags = 0; // default behaviour, allow flood and direct strcpy(wildcard.name, "(*)"); } @@ -100,7 +100,7 @@ RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL; // full! region = ®ions[num_regions++]; // alloc new RegionEntry - region->flags = 0; + region->flags = REGION_DENY_FLOOD; // DENY by default region->id = id == 0 ? next_id++ : id; StrHelper::strncpy(region->name, name, sizeof(region->name)); region->parent = parent_id; @@ -111,7 +111,7 @@ RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if ((region->flags & mask) == mask) { // does region allow this? (per 'mask' param) + if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param) TransportKey keys[4]; int num; if (region->name[0] == '#') { // auto hashtag region @@ -189,10 +189,10 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out.print(' '); } - if (parent->flags & REGION_ALLOW_FLOOD) { - out.printf("%s F\n", parent->name); - } else { + if (parent->flags & REGION_DENY_FLOOD) { out.printf("%s\n", parent->name); + } else { + out.printf("%s F\n", parent->name); } for (int i = 0; i < num_regions; i++) { diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 3858bfbde4..5ad9df29c2 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -8,7 +8,8 @@ #define MAX_REGION_ENTRIES 32 #endif -#define REGION_ALLOW_FLOOD 0x01 +#define REGION_DENY_FLOOD 0x01 +#define REGION_DENY_DIRECT 0x02 // reserved for future struct RegionEntry { uint16_t id; From 937865c8fd8d3e82cf5078bc77226d725c556344 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 5 Nov 2025 14:56:18 +1100 Subject: [PATCH 086/409] * companion: new CMD_SET_FLOOD_SCOPE (54) --- examples/companion_radio/MyMesh.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3c882d189c..904cd87134 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -50,6 +50,7 @@ #define CMD_SEND_BINARY_REQ 50 #define CMD_FACTORY_RESET 51 #define CMD_SEND_PATH_DISCOVERY_REQ 52 +#define CMD_SET_FLOOD_SCOPE 54 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -1515,6 +1516,13 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_FILE_IO_ERROR); } + } else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE && len >= 2 && cmd_frame[1] == 0) { + if (len >= 2 + 16) { + memcpy(send_scope.key, &cmd_frame[2], sizeof(send_scope.key)); // set curr scope TransportKey + } else { + memset(send_scope.key, 0, sizeof(send_scope.key)); // set scope to null + } + writeOKFrame(); } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); From 3ef53e64a1fd0f7b654e949f3caf16d3a760c1ba Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 5 Nov 2025 15:34:23 +1100 Subject: [PATCH 087/409] * is_name_char() bug fix --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index dce0f18bf5..0bfb7c8918 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -848,7 +848,7 @@ void MyMesh::clearStats() { } static bool is_name_char(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= 'z') || c == '-' || c == '.' || c == '_' || c == '#'; + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; } void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { From 82b4c1e6b0771031d53c6c301567cb021577a9cd Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 6 Nov 2025 00:56:54 +1100 Subject: [PATCH 088/409] * new PAYLOAD_TYPE_CONTROL (11) * repeater: onControlDataRecv(), now responds to new CTL_TYPE_NODE_DISCOVER_REQ (zero hop only) * node prefs: new discovery_mod_timestamp (will be set to affect when node should respond to DISCOVERY_REQ's ) --- examples/simple_repeater/MyMesh.cpp | 32 +++++++++++++++++++++++++++++ examples/simple_repeater/MyMesh.h | 1 + src/Mesh.cpp | 24 ++++++++++++++++++++++ src/Mesh.h | 6 ++++++ src/Packet.h | 1 + src/helpers/CommonCLI.cpp | 6 ++++-- src/helpers/CommonCLI.h | 1 + 7 files changed, 69 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 0bfb7c8918..74fca86ce1 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -607,6 +607,38 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t return false; } +#define CTL_TYPE_NODE_DISCOVER_REQ 0x80 +#define CTL_TYPE_NODE_DISCOVER_RESP 0x90 + +void MyMesh::onControlDataRecv(mesh::Packet* packet) { + uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6) { + // TODO: apply rate limiting to these! + int i = 1; + uint8_t filter = packet->payload[i++]; + uint32_t tag; + memcpy(&tag, &packet->payload[i], 4); i += 4; + uint32_t since; + if (packet->payload_len >= i+4) { // optional since field + memcpy(&since, &packet->payload[i], 4); i += 4; + } else { + since = 0; + } + + if ((filter & (1 << ADV_TYPE_REPEATER)) != 0 && _prefs.discovery_mod_timestamp >= since) { + uint8_t data[6 + PUB_KEY_SIZE]; + data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_REPEATER; // low 4-bits for node type + data[1] = packet->_snr; // let sender know the inbound SNR ( x 4) + memcpy(&data[2], &tag, 4); // include tag from request, for client to match to + memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); + auto resp = createControlData(data, sizeof(data)); + if (resp) { + sendZeroHop(resp, getRetransmitDelay(resp)); // apply random delay, as multiple nodes can respond to this + } + } + } +} + MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 8333154176..fd0b4d6a74 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -157,6 +157,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len); void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + void onControlDataRecv(mesh::Packet* packet) override; public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 71b8eaeeea..f12715742d 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -68,6 +68,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { return ACTION_RELEASE; } + if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_CONTROL && (pkt->payload[0] & 0x80) != 0) { + if (pkt->path_len == 0) { + onControlDataRecv(pkt); + } + // just zero-hop control packets allowed (for this subset of payloads) + return ACTION_RELEASE; + } + if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { @@ -589,6 +597,22 @@ Packet* Mesh::createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags) { return packet; } +Packet* Mesh::createControlData(const uint8_t* data, size_t len) { + if (len > sizeof(Packet::payload)) return NULL; // invalid arg + + Packet* packet = obtainNewPacket(); + if (packet == NULL) { + MESH_DEBUG_PRINTLN("%s Mesh::createControlData(): error, packet pool empty", getLogDateTime()); + return NULL; + } + packet->header = (PAYLOAD_TYPE_CONTROL << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later + + memcpy(packet->payload, data, len); + packet->payload_len = len; + + return packet; +} + void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) { if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); diff --git a/src/Mesh.h b/src/Mesh.h index 70fa80f379..bc1ac54db9 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -133,6 +133,11 @@ class Mesh : public Dispatcher { */ virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { } + /** + * \brief A control packet has been received. + */ + virtual void onControlDataRecv(Packet* packet) { } + /** * \brief A packet with PAYLOAD_TYPE_RAW_CUSTOM has been received. */ @@ -185,6 +190,7 @@ class Mesh : public Dispatcher { Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len); Packet* createRawData(const uint8_t* data, size_t len); Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0); + Packet* createControlData(const uint8_t* data, size_t len); /** * \brief send a locally-generated Packet with flood routing diff --git a/src/Packet.h b/src/Packet.h index e52ab526a7..42d73f416c 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -27,6 +27,7 @@ namespace mesh { #define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra) #define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop #define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets +#define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet //... #define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 88327aa89e..caed368b22 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -69,7 +69,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { 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 - // 162 + file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 + // 166 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -146,7 +147,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { 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 - // 162 + file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 + // 166 file.close(); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 3cfca46c80..a665e014f9 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -46,6 +46,7 @@ struct NodePrefs { // persisted to file uint8_t gps_enabled; uint32_t gps_interval; // in seconds uint8_t advert_loc_policy; + uint32_t discovery_mod_timestamp; }; class CommonCLICallbacks { From 7419ed71f7a57f7bf6f2ec157d5222fa1fa2283c Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 6 Nov 2025 12:27:25 +1100 Subject: [PATCH 089/409] * region filtering now applied in allowPacketForward() --- examples/simple_repeater/MyMesh.cpp | 22 ++++++++++++++-------- examples/simple_repeater/MyMesh.h | 1 + src/Mesh.h | 1 + 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 74fca86ce1..2fb0102969 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -306,6 +306,10 @@ File MyMesh::openAppend(const char *fname) { bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && recv_pkt_region == NULL) { + MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); + return false; + } return true; } @@ -405,19 +409,19 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { } bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { + // just try to determine region for packet (apply later in allowPacketForward()) if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { - auto region = region_map.findMatch(pkt, REGION_DENY_FLOOD); - if (region == NULL) { - MESH_DEBUG_PRINTLN("onRecvPacket: unknown transport code for FLOOD packet"); - return true; - } + recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD); } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { if (region_map.getWildcard().flags & REGION_DENY_FLOOD) { - MESH_DEBUG_PRINTLN("onRecvPacket: wildcard FLOOD packet not allowed"); - return true; + recv_pkt_region = NULL; + } else { + recv_pkt_region = ®ion_map.getWildcard(); } + } else { + recv_pkt_region = NULL; } - // otherwise do normal processing + // do normal processing return false; } @@ -973,6 +977,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply load_stack[0] = &temp_map.getWildcard(); region_load_active = true; } else if (n >= 2 && strcmp(parts[1], "save") == 0) { + _prefs.discovery_mod_timestamp = rtc_clock.getCurrentTime(); // this node is now 'modified' (for discovery info) + savePrefs(); bool success = region_map.save(_fs); strcpy(reply, success ? "OK" : "Err - save failed"); } else if (n >= 2 && strcmp(parts[1], "allowf") == 0) { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index fd0b4d6a74..450015973f 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -91,6 +91,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { TransportKeyStore key_store; RegionMap region_map, temp_map; RegionEntry* load_stack[8]; + RegionEntry* recv_pkt_region; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS diff --git a/src/Mesh.h b/src/Mesh.h index bc1ac54db9..00f7ed00f4 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -44,6 +44,7 @@ class Mesh : public Dispatcher { DispatcherAction routeRecvPacket(Packet* packet); /** + * \brief Called _before_ the packet is dispatched to the on..Recv() methods. * \returns true, if given packet should be NOT be processed. */ virtual bool filterRecvFloodPacket(Packet* packet) { return false; } From cf547da85735f86df5836e6803708845a7677ac8 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 6 Nov 2025 17:28:45 +1100 Subject: [PATCH 090/409] * RegionMap: get/set Home Region * repeater: admin CLI, changed "allowf *", "denyf *", added "home" --- examples/simple_repeater/MyMesh.cpp | 21 ++++++++++++++----- src/helpers/RegionMap.cpp | 32 +++++++++++++++++++++-------- src/helpers/RegionMap.h | 4 +++- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 2fb0102969..7489d61199 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -637,7 +637,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); auto resp = createControlData(data, sizeof(data)); if (resp) { - sendZeroHop(resp, getRetransmitDelay(resp)); // apply random delay, as multiple nodes can respond to this + sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this } } } @@ -981,16 +981,16 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply savePrefs(); bool success = region_map.save(_fs); strcpy(reply, success ? "OK" : "Err - save failed"); - } else if (n >= 2 && strcmp(parts[1], "allowf") == 0) { - auto region = n >= 3 ? region_map.findByNamePrefix(parts[2]) : ®ion_map.getWildcard(); + } else if (n >= 3 && strcmp(parts[1], "allowf") == 0) { + auto region = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); if (region) { region->flags &= ~REGION_DENY_FLOOD; strcpy(reply, "OK"); } else { strcpy(reply, "Err - unknown region"); } - } else if (n >= 2 && strcmp(parts[1], "denyf") == 0) { - auto region = n >= 3 ? region_map.findByNamePrefix(parts[2]) : ®ion_map.getWildcard(); + } else if (n >= 3 && strcmp(parts[1], "denyf") == 0) { + auto region = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); if (region) { region->flags |= REGION_DENY_FLOOD; strcpy(reply, "OK"); @@ -1004,6 +1004,17 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - unknown region"); } + } else if (n >= 3 && strcmp(parts[1], "home") == 0) { + auto home = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); + if (home) { + region_map.setHomeRegion(home); + sprintf(reply, " home is now %s", home->name); + } else { + strcpy(reply, "Err - unknown region"); + } + } else if (n == 2 && strcmp(parts[1], "home") == 0) { + auto home = region_map.getHomeRegion(); + sprintf(reply, " home is %s", home ? home->name : "*"); } else { strcpy(reply, "Err - ??"); } diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 074084ec1f..c6221db008 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -3,10 +3,10 @@ #include RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { - next_id = 1; num_regions = 0; + next_id = 1; num_regions = 0; home_id = 0; wildcard.id = wildcard.parent = 0; wildcard.flags = 0; // default behaviour, allow flood and direct - strcpy(wildcard.name, "(*)"); + strcpy(wildcard.name, "*"); } static File openWrite(FILESYSTEM* _fs, const char* filename) { @@ -31,9 +31,10 @@ bool RegionMap::load(FILESYSTEM* _fs) { if (file) { uint8_t pad[128]; - num_regions = 0; next_id = 1; + num_regions = 0; next_id = 1; home_id = 0; - bool success = file.read(pad, 7) == 7; // reserved header + bool success = file.read(pad, 5) == 5; // reserved header + success = success && file.read((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id); success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags); success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id); @@ -68,7 +69,8 @@ bool RegionMap::save(FILESYSTEM* _fs) { uint8_t pad[128]; memset(pad, 0, sizeof(pad)); - bool success = file.write(pad, 7) == 7; // reserved header + bool success = file.write(pad, 5) == 5; // reserved header + success = success && file.write((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id); success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags); success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id); @@ -140,11 +142,15 @@ RegionEntry* RegionMap::findByName(const char* name) { } RegionEntry* RegionMap::findByNamePrefix(const char* prefix) { + RegionEntry* partial = NULL; for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if (memcmp(prefix, region->name, strlen(prefix)) == 0) return region; + if (strcmp(prefix, region->name) == 0) return region; // is a complete match, preference this one + if (memcmp(prefix, region->name, strlen(prefix)) == 0) { + partial = region; + } } - return NULL; // not found + return partial; } RegionEntry* RegionMap::findById(uint16_t id) { @@ -157,6 +163,14 @@ RegionEntry* RegionMap::findById(uint16_t id) { return NULL; // not found } +RegionEntry* RegionMap::getHomeRegion() { + return findById(home_id); +} + +void RegionMap::setHomeRegion(const RegionEntry* home) { + home_id = home ? home->id : 0; +} + bool RegionMap::removeRegion(const RegionEntry& region) { if (region.id == 0) return false; // failed (cannot remove the wildcard Region) @@ -190,9 +204,9 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& } if (parent->flags & REGION_DENY_FLOOD) { - out.printf("%s\n", parent->name); + out.printf("%s%s\n", parent->name, parent->id == home_id ? "^" : ""); } else { - out.printf("%s F\n", parent->name); + out.printf("%s%s F\n", parent->name, parent->id == home_id ? "^" : ""); } for (int i = 0; i < num_regions; i++) { diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 5ad9df29c2..c3f897c538 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -20,7 +20,7 @@ struct RegionEntry { class RegionMap { TransportKeyStore* _store; - uint16_t next_id; + uint16_t next_id, home_id; uint16_t num_regions; RegionEntry regions[MAX_REGION_ENTRIES]; RegionEntry wildcard; @@ -39,6 +39,8 @@ class RegionMap { RegionEntry* findByName(const char* name); RegionEntry* findByNamePrefix(const char* prefix); RegionEntry* findById(uint16_t id); + RegionEntry* getHomeRegion(); // NOTE: can be NULL + void setHomeRegion(const RegionEntry* home); bool removeRegion(const RegionEntry& region); bool clear(); void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; } From 09eab330a2f3f22008948aa244a81e29462a985d Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 6 Nov 2025 20:15:01 +1100 Subject: [PATCH 091/409] * repeater: onAnonDataRecv(), now rejecting non-ASCII password (preparing for future request codes) * repeater: DISCOVER requests now with a simple RateLimiter (max 4, every 2 minutes) --- examples/simple_repeater/MyMesh.cpp | 15 +++++++++++---- examples/simple_repeater/MyMesh.h | 2 ++ examples/simple_repeater/RateLimiter.h | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 examples/simple_repeater/RateLimiter.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 7489d61199..9d313d212f 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -433,7 +433,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m memcpy(×tamp, data, 4); data[len] = 0; // ensure null terminator - uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + uint8_t reply_len; + if (data[0] == 0 || data[0] >= ' ') { // is password, ie. a login request + reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + //} else if (data[0] == ANON_REQ_TYPE_*) { // future type codes + // TODO + } else { + reply_len = 0; // unknown request type + } if (reply_len == 0) return; // invalid request @@ -616,8 +623,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t void MyMesh::onControlDataRecv(mesh::Packet* packet) { uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits - if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6) { - // TODO: apply rate limiting to these! + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) { int i = 1; uint8_t filter = packet->payload[i++]; uint32_t tag; @@ -646,7 +652,8 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), + discover_limiter(4, 120) // max 4 every 2 minutes #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 450015973f..86fac3f41c 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -33,6 +33,7 @@ #include #include #include +#include "RateLimiter.h" #ifdef WITH_BRIDGE extern AbstractBridge* bridge; @@ -92,6 +93,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionMap region_map, temp_map; RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; + RateLimiter discover_limiter; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS diff --git a/examples/simple_repeater/RateLimiter.h b/examples/simple_repeater/RateLimiter.h new file mode 100644 index 0000000000..a6633c0a26 --- /dev/null +++ b/examples/simple_repeater/RateLimiter.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class RateLimiter { + uint32_t _start_timestamp; + uint32_t _secs; + uint16_t _maximum, _count; + +public: + RateLimiter(uint16_t maximum, uint32_t secs): _maximum(maximum), _secs(secs), _start_timestamp(0), _count(0) { } + + bool allow(uint32_t now) { + if (now < _start_timestamp + _secs) { + _count++; + if (_count > _maximum) return false; // deny + } else { // time window now expired + _start_timestamp = now; + _count = 1; + } + return true; + } +}; \ No newline at end of file From 256848208d6303b2f11f16d5026d027a3be7c0e8 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 6 Nov 2025 20:22:40 +1100 Subject: [PATCH 092/409] * repeater: onAnonDataRecv(), future code check bug fix (offset 4) * sensor: onAnonDataRecv(), future request code provision --- examples/simple_repeater/MyMesh.cpp | 4 ++-- examples/simple_sensor/SensorMesh.cpp | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 9d313d212f..a996cbf630 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -434,9 +434,9 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; - if (data[0] == 0 || data[0] >= ' ') { // is password, ie. a login request + if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); - //} else if (data[0] == ANON_REQ_TYPE_*) { // future type codes + //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes // TODO } else { reply_len = 0; // unknown request type diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 6564c4ef9c..58bce7664b 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -449,7 +449,14 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con memcpy(×tamp, data, 4); data[len] = 0; // ensure null terminator - uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + uint8_t reply_len; + if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request + reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes + // TODO + } else { + reply_len = 0; // unknown request type + } if (reply_len == 0) return; // invalid request From ddac13ae80c74960a0879975741236f9021dc2cd Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 6 Nov 2025 21:40:52 +1100 Subject: [PATCH 093/409] * repeater: CLI, added "region put" and "region remove" commands --- examples/simple_repeater/MyMesh.cpp | 29 ++++++++++++++++++++++++----- src/helpers/RegionMap.cpp | 13 ++++++++++++- src/helpers/RegionMap.h | 2 ++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index a996cbf630..b06ae42a82 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -890,10 +890,6 @@ void MyMesh::clearStats() { ((SimpleMeshTables *)getTables())->resetStats(); } -static bool is_name_char(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; -} - void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { if (region_load_active) { if (*command == 0) { // empty line, signal to terminate 'load' operation @@ -907,7 +903,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply int indent = np - command; char *ep = np; - while (is_name_char(*ep)) ep++; + while (RegionMap::is_name_char(*ep)) ep++; if (*ep) { *ep++ = 0; } // set null terminator for end of name while (*ep && *ep != 'F') ep++; // look for (optional) flags @@ -1022,6 +1018,29 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else if (n == 2 && strcmp(parts[1], "home") == 0) { auto home = region_map.getHomeRegion(); sprintf(reply, " home is %s", home ? home->name : "*"); + } else if (n >= 3 && strcmp(parts[1], "put") == 0) { + auto parent = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard(); + if (parent == NULL) { + strcpy(reply, "Err - unknown parent"); + } else { + auto region = region_map.putRegion(parts[2], parent->id); + if (region == NULL) { + strcpy(reply, "Err - unable to put"); + } else { + strcpy(reply, "OK"); + } + } + } else if (n >= 3 && strcmp(parts[1], "remove") == 0) { + auto region = region_map.findByName(parts[2]); + if (region) { + if (region_map.removeRegion(*region)) { + strcpy(reply, "OK"); + } else { + strcpy(reply, "Err - not empty"); + } + } else { + strcpy(reply, "Err - not found"); + } } else { strcpy(reply, "Err - ??"); } diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index c6221db008..7d1c08e6b3 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -9,6 +9,10 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { strcpy(wildcard.name, "*"); } +bool RegionMap::is_name_char(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; +} + static File openWrite(FILESYSTEM* _fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) _fs->remove(filename); @@ -93,6 +97,12 @@ bool RegionMap::save(FILESYSTEM* _fs) { } RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) { + const char* sp = name; // check for illegal name chars + while (*sp) { + if (!is_name_char(*sp)) return NULL; // error + sp++; + } + auto region = findByName(name); if (region) { if (region->id == parent_id) return NULL; // ERROR: invalid parent! @@ -187,8 +197,9 @@ bool RegionMap::removeRegion(const RegionEntry& region) { if (i >= num_regions) return false; // failed (not found) num_regions--; // remove from regions array - while (i + 1 < num_regions) { + while (i < num_regions) { regions[i] = regions[i + 1]; + i++; } return true; // success } diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index c3f897c538..50513be1c5 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -30,6 +30,8 @@ class RegionMap { public: RegionMap(TransportKeyStore& store); + static bool is_name_char(char c); + bool load(FILESYSTEM* _fs); bool save(FILESYSTEM* _fs); From 4a5404d997ce89dae793e47b498f076b9a244d76 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 6 Nov 2025 22:10:20 +1100 Subject: [PATCH 094/409] * companion: added CMD_SEND_CONTROL_DATA, and PUSH_CODE_CONTROL_DATA --- examples/companion_radio/MyMesh.cpp | 30 +++++++++++++++++++++++++++++ examples/companion_radio/MyMesh.h | 1 + 2 files changed, 31 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 904cd87134..16be3e9c83 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -51,6 +51,7 @@ #define CMD_FACTORY_RESET 51 #define CMD_SEND_PATH_DISCOVERY_REQ 52 #define CMD_SET_FLOOD_SCOPE 54 +#define CMD_SEND_CONTROL_DATA 55 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -100,6 +101,7 @@ #define PUSH_CODE_TELEMETRY_RESPONSE 0x8B #define PUSH_CODE_BINARY_RESPONSE 0x8C #define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D +#define PUSH_CODE_CONTROL_DATA 0x8E #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -626,6 +628,26 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len); } +void MyMesh::onControlDataRecv(mesh::Packet *packet) { + if (packet->payload_len + 4 > sizeof(out_frame)) { + MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len); + return; + } + int i = 0; + out_frame[i++] = PUSH_CODE_CONTROL_DATA; + out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4); + out_frame[i++] = (int8_t)(_radio->getLastRSSI()); + out_frame[i++] = packet->path_len; + memcpy(&out_frame[i], packet->payload, packet->payload_len); + i += packet->payload_len; + + if (_serial->isConnected()) { + _serial->writeFrame(out_frame, i); + } else { + MESH_DEBUG_PRINTLN("onControlDataRecv(), data received while app offline"); + } +} + void MyMesh::onRawDataRecv(mesh::Packet *packet) { if (packet->payload_len + 4 > sizeof(out_frame)) { MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len); @@ -1523,6 +1545,14 @@ void MyMesh::handleCmdFrame(size_t len) { memset(send_scope.key, 0, sizeof(send_scope.key)); // set scope to null } writeOKFrame(); + } else if (cmd_frame[0] == CMD_SEND_CONTROL_DATA && len >= 2 && (cmd_frame[1] & 0x80) != 0) { + auto resp = createControlData(&cmd_frame[1], len - 1); + if (resp) { + sendZeroHop(resp); + writeOKFrame(); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index fb0fb479d9..927f22e4ea 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -133,6 +133,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data, uint8_t len, uint8_t *reply) override; void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override; + void onControlDataRecv(mesh::Packet *packet) override; void onRawDataRecv(mesh::Packet *packet) override; void onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags, const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) override; From 2e63499ae594e14842a3be35f5678389b748ad0a Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 6 Nov 2025 22:51:17 +1100 Subject: [PATCH 095/409] * companion: protocol ver bumped to 8. --- examples/companion_radio/MyMesh.cpp | 6 +++--- examples/companion_radio/MyMesh.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 16be3e9c83..598d535f77 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -50,8 +50,8 @@ #define CMD_SEND_BINARY_REQ 50 #define CMD_FACTORY_RESET 51 #define CMD_SEND_PATH_DISCOVERY_REQ 52 -#define CMD_SET_FLOOD_SCOPE 54 -#define CMD_SEND_CONTROL_DATA 55 +#define CMD_SET_FLOOD_SCOPE 54 // v8+ +#define CMD_SEND_CONTROL_DATA 55 // v8+ #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -101,7 +101,7 @@ #define PUSH_CODE_TELEMETRY_RESPONSE 0x8B #define PUSH_CODE_BINARY_RESPONSE 0x8C #define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D -#define PUSH_CODE_CONTROL_DATA 0x8E +#define PUSH_CODE_CONTROL_DATA 0x8E // v8+ #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 927f22e4ea..f2b56e5e3a 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -5,7 +5,7 @@ #include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 7 +#define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "2 Oct 2025" From 06825030e5d48098857a63a26165cb88d0221218 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 6 Nov 2025 22:36:37 +0100 Subject: [PATCH 096/409] sensor: copy control data code from repeater --- examples/simple_sensor/SensorMesh.cpp | 32 +++++++++++++++++++++++++++ examples/simple_sensor/SensorMesh.h | 1 + 2 files changed, 33 insertions(+) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 58bce7664b..2ab2081f04 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -617,6 +617,38 @@ bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t return false; } +#define CTL_TYPE_NODE_DISCOVER_REQ 0x80 +#define CTL_TYPE_NODE_DISCOVER_RESP 0x90 + +void SensorMesh::onControlDataRecv(mesh::Packet* packet) { + uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6) { + // TODO: apply rate limiting to these! + int i = 1; + uint8_t filter = packet->payload[i++]; + uint32_t tag; + memcpy(&tag, &packet->payload[i], 4); i += 4; + uint32_t since; + if (packet->payload_len >= i+4) { // optional since field + memcpy(&since, &packet->payload[i], 4); i += 4; + } else { + since = 0; + } + + if ((filter & (1 << ADV_TYPE_SENSOR)) != 0 && _prefs.discovery_mod_timestamp >= since) { + uint8_t data[6 + PUB_KEY_SIZE]; + data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_SENSOR; // low 4-bits for node type + data[1] = packet->_snr; // let sender know the inbound SNR ( x 4) + memcpy(&data[2], &tag, 4); // include tag from request, for client to match to + memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); + auto resp = createControlData(data, sizeof(data)); + if (resp) { + sendZeroHop(resp, getRetransmitDelay(resp)); // apply random delay, as multiple nodes can respond to this + } + } + } +} + bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= acl.getNumClients()) { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index ba55bc7011..0fb00dc160 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -125,6 +125,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + void onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len); void sendAckTo(const ClientInfo& dest, uint32_t ack_hash); From 963290ea159c00edf4283bafd71464b27c8d95dc Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 7 Nov 2025 14:42:06 +1100 Subject: [PATCH 097/409] * repeater: various "region" CLI changes * transport codes 0000 and FFFF reserved --- examples/simple_repeater/MyMesh.cpp | 17 +++++++++++------ src/helpers/RegionMap.cpp | 4 ++++ src/helpers/TransportKeyStore.cpp | 5 +++++ src/helpers/TxtDataHelpers.cpp | 7 +++++++ src/helpers/TxtDataHelpers.h | 1 + 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b06ae42a82..cae05b209b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -892,7 +892,7 @@ void MyMesh::clearStats() { void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { if (region_load_active) { - if (*command == 0) { // empty line, signal to terminate 'load' operation + if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation region_map = temp_map; // copy over the temp instance as new current map region_load_active = false; @@ -908,7 +908,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply while (*ep && *ep != 'F') ep++; // look for (optional) flags - if (indent > 0 && indent < 8) { + if (indent > 0 && indent < 8 && strlen(np) > 0) { auto parent = load_stack[indent - 1]; if (parent) { auto old = region_map.findByName(np); @@ -985,7 +985,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply bool success = region_map.save(_fs); strcpy(reply, success ? "OK" : "Err - save failed"); } else if (n >= 3 && strcmp(parts[1], "allowf") == 0) { - auto region = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); + auto region = region_map.findByNamePrefix(parts[2]); if (region) { region->flags &= ~REGION_DENY_FLOOD; strcpy(reply, "OK"); @@ -993,7 +993,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply strcpy(reply, "Err - unknown region"); } } else if (n >= 3 && strcmp(parts[1], "denyf") == 0) { - auto region = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); + auto region = region_map.findByNamePrefix(parts[2]); if (region) { region->flags |= REGION_DENY_FLOOD; strcpy(reply, "OK"); @@ -1003,12 +1003,17 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else if (n >= 3 && strcmp(parts[1], "get") == 0) { auto region = region_map.findByNamePrefix(parts[2]); if (region) { - sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F"); + auto parent = region_map.findById(region->parent); + if (parent && parent->id != 0) { + sprintf(reply, " %s (%s) %s", region->name, parent->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F"); + } else { + sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F"); + } } else { strcpy(reply, "Err - unknown region"); } } else if (n >= 3 && strcmp(parts[1], "home") == 0) { - auto home = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); + auto home = region_map.findByNamePrefix(parts[2]); if (home) { region_map.setHomeRegion(home); sprintf(reply, " home is now %s", home->name); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 7d1c08e6b3..368446157b 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -144,6 +144,8 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { } RegionEntry* RegionMap::findByName(const char* name) { + if (strcmp(name, "*") == 0) return &wildcard; + for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; if (strcmp(name, region->name) == 0) return region; @@ -152,6 +154,8 @@ RegionEntry* RegionMap::findByName(const char* name) { } RegionEntry* RegionMap::findByNamePrefix(const char* prefix) { + if (strcmp(prefix, "*") == 0) return &wildcard; + RegionEntry* partial = NULL; for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; diff --git a/src/helpers/TransportKeyStore.cpp b/src/helpers/TransportKeyStore.cpp index 8b7c891bc5..f34610b65d 100644 --- a/src/helpers/TransportKeyStore.cpp +++ b/src/helpers/TransportKeyStore.cpp @@ -9,6 +9,11 @@ uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const { sha.update(&type, 1); sha.update(packet->payload, packet->payload_len); sha.finalizeHMAC(key, sizeof(key), &code, 2); + if (code == 0) { // reserve codes 0000 and FFFF + code++; + } else if (code == 0xFFFF) { + code--; + } return code; } diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index 0044fd2869..224eb8731d 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -19,6 +19,13 @@ void StrHelper::strzcpy(char* dest, const char* src, size_t buf_sz) { } } +bool StrHelper::isBlank(const char* str) { + while (*str) { + if (*str++ != ' ') return false; + } + return true; +} + #include union int32_Float_t diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 3154766cd4..89789990d3 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -12,4 +12,5 @@ class StrHelper { static void strncpy(char* dest, const char* src, size_t buf_sz); static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls static const char* ftoa(float f); + static bool isBlank(const char* str); }; From 62d7ce110b912b55a5e1fd5107ecfabaac7cb694 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 7 Nov 2025 15:49:01 +1100 Subject: [PATCH 098/409] * packet format docs updated --- docs/packet_structure.md | 4 +++ docs/payloads.md | 56 +++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/docs/packet_structure.md b/docs/packet_structure.md index aa260855f4..92c410be55 100644 --- a/docs/packet_structure.md +++ b/docs/packet_structure.md @@ -44,6 +44,10 @@ bit 0 means the lowest bit (1s place) | `0x08` | `PAYLOAD_TYPE_PATH` | Returned path. | | `0x09` | `PAYLOAD_TYPE_TRACE` | trace a path, collecting SNI for each hop. | | `0x0A` | `PAYLOAD_TYPE_MULTIPART` | packet is part of a sequence of packets. | +| `0x0B` | `PAYLOAD_TYPE_CONTROL` | control packet data (unencrypted) | +| `0x0C` | . | reserved | +| `0x0D` | . | reserved | +| `0x0E` | . | reserved | | `0x0F` | `PAYLOAD_TYPE_RAW_CUSTOM` | Custom packet (raw bytes, custom encryption). | ## Payload Version Values diff --git a/docs/payloads.md b/docs/payloads.md index 4d00f93005..f66088f7cc 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -11,6 +11,7 @@ Inside of each [meshcore packet](./packet_structure.md) is a payload, identified * Group text message (unverified). * Group datagram (unverified). * Multi-part packet +* Control data packet * Custom packet (raw bytes, custom encryption). This document defines the structure of each of these payload types. @@ -57,7 +58,7 @@ Appdata Flags # Acknowledgement -An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement will be sent in the "extra" payload (see [Returned Path](#returned-path)) and not as a discrete ackowledgement. CLI commands do not require an acknowledgement, neither discrete nor extra. +An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement can be sent in the "extra" payload (see [Returned Path](#returned-path)) instead of as a separate ackowledgement packet. CLI commands do not cause acknowledgement responses, neither discrete nor extra. | Field | Size (bytes) | Description | |----------|--------------|------------------------------------------------------------| @@ -140,13 +141,13 @@ Request data about sensors on the node, including battery level. ## Plain text message -| Field | Size (bytes) | Description | -|-----------------|-----------------|--------------------------------------------------------------| -| timestamp | 4 | send time (unix timestamp) | -| flags + attempt | 1 | upper six bits are flags (see below), lower two bits are attempt number (0..3) | -| message | rest of payload | the message content, see next table | +| Field | Size (bytes) | Description | +|--------------------|-----------------|--------------------------------------------------------------| +| timestamp | 4 | send time (unix timestamp) | +| txt_type + attempt | 1 | upper six bits are txt_type (see below), lower two bits are attempt number (0..3) | +| message | rest of payload | the message content, see next table | -Flags +txt_type | Value | Description | Message content | |--------|---------------------------|------------------------------------------------------------| @@ -163,13 +164,20 @@ Flags | cipher MAC | 2 | MAC for encrypted data in next field | | ciphertext | rest of payload | encrypted message, see below for details | -Plaintext message +## Room server login | Field | Size (bytes) | Description | |----------------|-----------------|-------------------------------------------------------------------------------| -| timestamp | 4 | send time (unix timestamp) | -| sync timestamp | 4 | NOTE: room server only! - sender's "sync messages SINCE x" timestamp | -| password | rest of message | password for repeater/room | +| timestamp | 4 | sender time (unix timestamp) | +| sync timestamp | 4 | sender's "sync messages SINCE x" timestamp | +| password | rest of message | password for room | + +## Repeater/Sensor login + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| password | rest of message | password for repeater/sensor | # Group text message / datagram @@ -182,7 +190,31 @@ Plaintext message The plaintext contained in the ciphertext matches the format described in [plain text message](#plain-text-message). Specifically, it consists of a four byte timestamp, a flags byte, and the message. The flags byte will generally be `0x00` because it is a "plain text message". The message will be of the form `: ` (eg., `user123: I'm on my way`). -TODO: describe what datagram looks like +# Control data + +| Field | Size (bytes) | Description | +|--------------|-----------------|--------------------------------------------| +| flags | 1 | upper 4 bits is sub_type | +| data | rest of payload | typically unencrypted data | + +## DISCOVER_REQ (sub_type) + +| Field | Size (bytes) | Description | +|--------------|-----------------|--------------------------------------------| +| flags | 1 | 0x8 (upper 4 bits) | +| type_filter | 1 | bit for each ADV_TYPE_* | +| tag | 4 | randomly generate by sender | +| since | 4 | (optional) epoch timestamp (0 by default) | + +## DISCOVER_RESP (sub_type) + +| Field | Size (bytes) | Description | +|--------------|-----------------|--------------------------------------------| +| flags | 1 | 0x9 (upper 4 bits), node_type (lower 4) | +| snr | 1 | signed, SNR*4 | +| tag | 4 | reflected back from DISCOVER_REQ | +| pubkey | 32 | node's ID | + # Custom packet From 1520f4d28e3b0e512155567bdcfd12ac54922c94 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 7 Nov 2025 19:31:09 +1100 Subject: [PATCH 099/409] * repeater, DISCOVER_REQ, flags lowest bit now for 'prefix_only' responses --- docs/payloads.md | 14 +++++++------- examples/simple_repeater/MyMesh.cpp | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/payloads.md b/docs/payloads.md index f66088f7cc..5a41e69cef 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -199,12 +199,12 @@ The plaintext contained in the ciphertext matches the format described in [plain ## DISCOVER_REQ (sub_type) -| Field | Size (bytes) | Description | -|--------------|-----------------|--------------------------------------------| -| flags | 1 | 0x8 (upper 4 bits) | -| type_filter | 1 | bit for each ADV_TYPE_* | -| tag | 4 | randomly generate by sender | -| since | 4 | (optional) epoch timestamp (0 by default) | +| Field | Size (bytes) | Description | +|--------------|-----------------|----------------------------------------------| +| flags | 1 | 0x8 (upper 4 bits), prefix_only (lowest bit) | +| type_filter | 1 | bit for each ADV_TYPE_* | +| tag | 4 | randomly generate by sender | +| since | 4 | (optional) epoch timestamp (0 by default) | ## DISCOVER_RESP (sub_type) @@ -213,7 +213,7 @@ The plaintext contained in the ciphertext matches the format described in [plain | flags | 1 | 0x9 (upper 4 bits), node_type (lower 4) | | snr | 1 | signed, SNR*4 | | tag | 4 | reflected back from DISCOVER_REQ | -| pubkey | 32 | node's ID | +| pubkey | 8 or 32 | node's ID (or prefix) | # Custom packet diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index cae05b209b..622b73a6da 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -636,12 +636,13 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { } if ((filter & (1 << ADV_TYPE_REPEATER)) != 0 && _prefs.discovery_mod_timestamp >= since) { + bool prefix_only = packet->payload[0] & 1; uint8_t data[6 + PUB_KEY_SIZE]; data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_REPEATER; // low 4-bits for node type data[1] = packet->_snr; // let sender know the inbound SNR ( x 4) memcpy(&data[2], &tag, 4); // include tag from request, for client to match to memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); - auto resp = createControlData(data, sizeof(data)); + auto resp = createControlData(data, prefix_only ? 6 + 8 : 6 + PUB_KEY_SIZE); if (resp) { sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this } From c0a51aff66ee53949e6a499d270cd00e6809dab7 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:47:18 +0100 Subject: [PATCH 100/409] change ADC_MULTIPLIER to better reflect battery voltage --- variants/thinknode_m2/variant.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/variants/thinknode_m2/variant.h b/variants/thinknode_m2/variant.h index 928f56ec03..223bfbb563 100644 --- a/variants/thinknode_m2/variant.h +++ b/variants/thinknode_m2/variant.h @@ -1,8 +1,8 @@ #define I2C_SCL 15 #define I2C_SDA 16 -#define PIN_VBAT_READ 17 +#define PIN_VBAT_READ 17 #define AREF_VOLTAGE (3.0) -#define ADC_MULTIPLIER (1.548F) +#define ADC_MULTIPLIER (1.509F) #define PIN_BUZZER 5 #define PIN_VEXT_EN_ACTIVE HIGH #define PIN_VEXT_EN 46 @@ -10,6 +10,3 @@ #define PIN_LED 6 #define PIN_STATUS_LED 6 #define PIN_PWRBTN 4 - - - From 429f82106bbb16a353d0aa5e86be8fbf18aa2972 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:48:57 +0100 Subject: [PATCH 101/409] tweak getBattMilliVolts to report battery more accurately --- variants/thinknode_m2/ThinknodeM2Board.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp index 3a2f067e28..332f1f670f 100644 --- a/variants/thinknode_m2/ThinknodeM2Board.cpp +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -19,14 +19,21 @@ void ThinknodeM2Board::begin() { enterDeepSleep(0); } - uint16_t ThinknodeM2Board::getBattMilliVolts() { + uint16_t ThinknodeM2Board::getBattMilliVolts() { analogReadResolution(12); - delay(10); - float volts = (analogRead(PIN_VBAT_READ) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096; - analogReadResolution(10); - return volts * 1000; - } + analogSetPinAttenuation(PIN_VBAT_READ, ADC_11db); + + uint32_t mv = 0; + for (int i = 0; i < 8; ++i) { + mv += analogReadMilliVolts(PIN_VBAT_READ); + delayMicroseconds(200); + } + mv /= 8; + + analogReadResolution(10); + return static_cast(mv * ADC_MULTIPLIER ); +} const char* ThinknodeM2Board::getManufacturerName() const { return "Elecrow ThinkNode M2"; - } \ No newline at end of file + } From a0bf66f9d83dbe75353d665de827f503d533c600 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:50:21 +0100 Subject: [PATCH 102/409] Fix for display not coming on after poweron --- variants/thinknode_m2/ThinknodeM2Board.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp index 332f1f670f..0596510361 100644 --- a/variants/thinknode_m2/ThinknodeM2Board.cpp +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -3,12 +3,13 @@ void ThinknodeM2Board::begin() { + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle + delay(20); // allow power rail to discharge + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on + delay(120); // give display time to bias on cold boot ESP32Board::begin(); - pinMode(PIN_VEXT_EN, OUTPUT); // init display - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // pin needs to be high - delay(10); - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // need to do this twice. do not know why.. - pinMode(PIN_STATUS_LED, OUTPUT); // init power led + pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { From 00e0635ab5400c9fc78bf05c729ddb1f234f3a9a Mon Sep 17 00:00:00 2001 From: brad Date: Sat, 8 Nov 2025 20:05:46 -0600 Subject: [PATCH 103/409] add variant files for ikoka handheld (nrf52 with e22 radio) --- .../ikoka_handheld_nrf/IkokaNrf52Board.cpp | 100 ++++++++++++ variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 52 ++++++ variants/ikoka_handheld_nrf/platformio.ini | 103 ++++++++++++ variants/ikoka_handheld_nrf/target.cpp | 46 ++++++ variants/ikoka_handheld_nrf/target.h | 29 ++++ variants/ikoka_handheld_nrf/variant.cpp | 84 ++++++++++ variants/ikoka_handheld_nrf/variant.h | 148 ++++++++++++++++++ 7 files changed, 562 insertions(+) create mode 100644 variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp create mode 100644 variants/ikoka_handheld_nrf/IkokaNrf52Board.h create mode 100644 variants/ikoka_handheld_nrf/platformio.ini create mode 100644 variants/ikoka_handheld_nrf/target.cpp create mode 100644 variants/ikoka_handheld_nrf/target.h create mode 100644 variants/ikoka_handheld_nrf/variant.cpp create mode 100644 variants/ikoka_handheld_nrf/variant.h diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp new file mode 100644 index 0000000000..44940b8ff4 --- /dev/null +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp @@ -0,0 +1,100 @@ +#ifdef IKOKA_NRF52 + +#include +#include +#include + +#include "IkokaNrf52Board.h" + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void IkokaNrf52Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + // ensure we have pull ups on the screen i2c, this isn't always available + // in hardware and it should only be 20k ohms. Disable the pullups if we + // are using the rotated lcd breakout board + #if defined(DISPLAY_CLASS) && DISPLAY_ROTATION == 0 + pinMode(PIN_WIRE_SDA, INPUT_PULLUP); + pinMode(PIN_WIRE_SCL, INPUT_PULLUP); + #endif + + pinMode(PIN_VBAT, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, HIGH); + + // required button pullup is handled as part of button initilization + // in target.cpp + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +#endif + + delay(10); // give sx1262 some time to power up +} + +bool IkokaNrf52Board::startOTAUpdate(const char *id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("XIAO_NRF52_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + + return true; +} + +#endif diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h new file mode 100644 index 0000000000..9dfc3833ea --- /dev/null +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#ifdef IKOKA_NRF52 + +class IkokaNrf52Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + } +#endif + + uint16_t getBattMilliVolts() override { + // Please read befor going further ;) + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + + // We can't drive VBAT_ENABLE to HIGH as long + // as we don't know wether we are charging or not ... + // this is a 3mA loss (4/1500) + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char* getManufacturerName() const override { + return "Ikoka Handheld E22 30dBm (Xiao_nrf52)"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; + +#endif diff --git a/variants/ikoka_handheld_nrf/platformio.ini b/variants/ikoka_handheld_nrf/platformio.ini new file mode 100644 index 0000000000..e4643c3831 --- /dev/null +++ b/variants/ikoka_handheld_nrf/platformio.ini @@ -0,0 +1,103 @@ +[ikoka_nrf52] +extends = Xiao_nrf52 +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + densaugeo/base64 @ ~1.4.0 +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 + -I variants/ikoka_handheld_nrf + -UENV_INCLUDE_GPS + -D IKOKA_NRF52 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_TX_LED=11 + -D P_LORA_DIO_1=D1 + -D P_LORA_RESET=D2 + -D P_LORA_BUSY=D3 + -D P_LORA_NSS=D4 + -D SX126X_RXEN=D5 + -D SX126X_TXEN=RADIOLIB_NC + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/ikoka_handheld_nrf> + + + +# larger screen has a different driver, this is for the 0.96 inch +[ikoka_nrf52_ssd1306_companion] +lib_deps = ${ikoka_nrf52.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 +build_flags = ${ikoka_nrf52.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D DISPLAY_ROTATION=0 + -D PIN_WIRE_SCL=D6 + -D PIN_WIRE_SDA=D7 + -D PIN_USER_BTN=D0 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D QSPIFLASH=1 + -I examples/companion_radio/ui-new +build_src_filter = ${ikoka_nrf52.build_src_filter} + + + +<../examples/companion_radio/ui-new/UITask.cpp> + +<../examples/companion_radio/*.cpp> + +[env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_ble] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D BLE_PIN_CODE=123456 + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + + + +[env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_ble] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D BLE_PIN_CODE=123456 + -D LORA_TX_POWER=20 + -D DISPLAY_ROTATION=2 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + + + +[env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_usb] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + +[env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_usb] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D LORA_TX_POWER=20 + -D DISPLAY_ROTATION=2 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + +[env:ikoka_handheld_nrf_e22_30dbm_repeater] +extends = ikoka_nrf52 +build_flags = + ${ikoka_nrf52.build_flags} + -D ADVERT_NAME='"ikoka_handheld Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52.build_src_filter} + +<../examples/simple_repeater/*.cpp> + +[env:ikoka_handheld_nrf_e22_30dbm_room_server] +extends = ikoka_nrf52 +build_flags = + ${ikoka_nrf52.build_flags} + -D ADVERT_NAME='"ikoka_handheld Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52.build_src_filter} + +<../examples/simple_room_server/*.cpp> diff --git a/variants/ikoka_handheld_nrf/target.cpp b/variants/ikoka_handheld_nrf/target.cpp new file mode 100644 index 0000000000..efa6669f1f --- /dev/null +++ b/variants/ikoka_handheld_nrf/target.cpp @@ -0,0 +1,46 @@ +#include +#include "target.h" +#include + +IkokaNrf52Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); +#endif + + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/ikoka_handheld_nrf/target.h b/variants/ikoka_handheld_nrf/target.h new file mode 100644 index 0000000000..a28ca81a55 --- /dev/null +++ b/variants/ikoka_handheld_nrf/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + #include + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + + +extern IkokaNrf52Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_handheld_nrf/variant.cpp b/variants/ikoka_handheld_nrf/variant.cpp new file mode 100644 index 0000000000..38a94c895e --- /dev/null +++ b/variants/ikoka_handheld_nrf/variant.cpp @@ -0,0 +1,84 @@ +#include "variant.h" + +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) + + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) + + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) + + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) + + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) + + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) + + // VBAT + 31, // D32 is P0.31 (VBAT) +}; + +void initVariant() { + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + // digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); + + // disable xiao charging current, the handheld uses a tp4056 to charge + // instead of the onboard xiao charging circuit. This charges at a max of + // 780ma instead of 100ma. In theory you could enable both, but in practice + // fire is scary. + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, HIGH); + + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); +} diff --git a/variants/ikoka_handheld_nrf/variant.h b/variants/ikoka_handheld_nrf/variant.h new file mode 100644 index 0000000000..8e6a8ed187 --- /dev/null +++ b/variants/ikoka_handheld_nrf/variant.h @@ -0,0 +1,148 @@ +#ifndef _SEEED_XIAO_NRF52840_H_ +#define _SEEED_XIAO_NRF52840_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +//#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define PINS_COUNT (33) +#define NUM_DIGITAL_PINS (33) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (LED_RED) +#define LED_PWR (PINS_COUNT) +#define PIN_NEOPIXEL (PINS_COUNT) +#define NEOPIXEL_NUM (0) + +#define LED_BUILTIN (PIN_LED) + +#define LED_RED (11) +#define LED_GREEN (13) +#define LED_BLUE (12) + +#define LED_STATE_ON (1) // State when LED is litted + +// Buttons +#define PIN_BUTTON1 (PINS_COUNT) + +// Digital PINs +static const uint8_t D0 = 0 ; +static const uint8_t D1 = 1 ; +static const uint8_t D2 = 2 ; +static const uint8_t D3 = 3 ; +static const uint8_t D4 = 4 ; +static const uint8_t D5 = 5 ; +static const uint8_t D6 = 6 ; +static const uint8_t D7 = 7 ; +static const uint8_t D8 = 8 ; +static const uint8_t D9 = 9 ; +static const uint8_t D10 = 10; + +#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define PIN_CHARGING_CURRENT (22) // Battery Charging current + // https://wiki.seedstudio.com/XIAO_BLE#battery-charging-current +// Analog pins +#define PIN_A0 (0) +#define PIN_A1 (1) +#define PIN_A2 (2) +#define PIN_A3 (3) +#define PIN_A4 (4) +#define PIN_A5 (5) +#define PIN_VBAT (32) // Read the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define BAT_NOT_CHARGING (23) // LOW when charging + +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; + +#define ADC_RESOLUTION (12) + +// Other pins +#define PIN_NFC1 (30) +#define PIN_NFC2 (31) + +// Serial interfaces +#define PIN_SERIAL1_RX (7) +#define PIN_SERIAL1_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (9) +#define PIN_SPI_MOSI (10) +#define PIN_SPI_SCK (8) + +#define PIN_SPI1_MISO (25) +#define PIN_SPI1_MOSI (26) +#define PIN_SPI1_SCK (29) + +// Lora SPI is on SPI0 +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (6) // 4 and 5 are used for the sx1262 ! +#define PIN_WIRE_SCL (7) // use WIRE1_SDA + +static const uint8_t SDA = (6); +static const uint8_t SCL = (7); + +//#define PIN_WIRE1_SDA (17) +//#define PIN_WIRE1_SCL (16) +#define PIN_LSM6DS3TR_C_POWER (15) +#define PIN_LSM6DS3TR_C_INT1 (18) + +// PDM Interfaces +#define PIN_PDM_PWR (19) +#define PIN_PDM_CLK (20) +#define PIN_PDM_DIN (21) + +// QSPI Pins +#define PIN_QSPI_SCK (24) +#define PIN_QSPI_CS (25) +#define PIN_QSPI_IO0 (26) +#define PIN_QSPI_IO1 (27) +#define PIN_QSPI_IO2 (28) +#define PIN_QSPI_IO3 (29) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES (P25Q16H) +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From b31d3e7b5f90c7b6f562079df02e4a88cf93c97c Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 9 Nov 2025 16:17:49 +1100 Subject: [PATCH 104/409] * added StrHelper::fromHex() --- src/helpers/TxtDataHelpers.cpp | 20 ++++++++++++++++++++ src/helpers/TxtDataHelpers.h | 1 + 2 files changed, 21 insertions(+) diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index 224eb8731d..09e86c676e 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -139,3 +139,23 @@ const char* StrHelper::ftoa(float f) { } return tmp; } + +uint32_t StrHelper::fromHex(const char* src) { + uint32_t n = 0; + while (*src) { + if (*src >= '0' && *src <= '9') { + n <<= 4; + n |= (*src - '0'); + } else if (*src >= 'A' && *src <= 'F') { + n <<= 4; + n |= (*src - 'A' + 10); + } else if (*src >= 'a' && *src <= 'f') { + n <<= 4; + n |= (*src - 'a' + 10); + } else { + break; // non-hex char encountered, stop parsing + } + src++; + } + return n; +} diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 89789990d3..387e09b931 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -13,4 +13,5 @@ class StrHelper { static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls static const char* ftoa(float f); static bool isBlank(const char* str); + static uint32_t fromHex(const char* src); }; From ab0721d6dfaa66aa1271196ee7b7abe64c880686 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 9 Nov 2025 16:36:23 +1100 Subject: [PATCH 105/409] * fix: repeater and room server telemetry requests now return all telemetry for _READ & _WRITE ACL permissions. --- examples/simple_repeater/MyMesh.cpp | 5 ++++- examples/simple_room_server/MyMesh.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 622b73a6da..4136818cee 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -170,7 +170,10 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry); + if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { + perm_mask = 0x00; // just base telemetry allowed + } + sensors.querySensors(perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 4d953c9cde..de06b4c675 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -165,7 +165,10 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry); + if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { + perm_mask = 0x00; // just base telemetry allowed + } + sensors.querySensors(perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); From df4dab8509081a08010229af207ac2eae288bab8 Mon Sep 17 00:00:00 2001 From: agessaman Date: Fri, 7 Nov 2025 22:16:48 -0800 Subject: [PATCH 106/409] Add statistics commands and response handling in MyMesh - Introduced new commands for retrieving statistics: CMD_GET_STATS_CORE, CMD_GET_STATS_RADIO, and CMD_GET_STATS_PACKETS. - Implemented corresponding response handling methods to format and send statistics data. - Updated MyMesh constructor to initialize new member variables for managing statistics. - Included StatsFormatHelper for formatting statistics replies. --- examples/companion_radio/MyMesh.cpp | 62 ++++++++++++++++++++++++++++- examples/companion_radio/MyMesh.h | 8 ++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 598d535f77..fa9edc8c26 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -52,6 +52,9 @@ #define CMD_SEND_PATH_DISCOVERY_REQ 52 #define CMD_SET_FLOOD_SCOPE 54 // v8+ #define CMD_SEND_CONTROL_DATA 55 // v8+ +#define CMD_GET_STATS_CORE 56 +#define CMD_GET_STATS_RADIO 57 +#define CMD_GET_STATS_PACKETS 58 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -77,6 +80,9 @@ #define RESP_CODE_CUSTOM_VARS 21 #define RESP_CODE_ADVERT_PATH 22 #define RESP_CODE_TUNING_PARAMS 23 +#define RESP_CODE_STATS_CORE 24 +#define RESP_CODE_STATS_RADIO 25 +#define RESP_CODE_STATS_PACKETS 26 #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -704,7 +710,7 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui) - : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), + : BaseChatMesh(radio, *(_ms_clock = new ArduinoMillis()), rng, rtc, *(_pkt_mgr = new StaticPoolPacketManager(16)), tables), _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) { _iter_started = false; _cli_rescue = false; @@ -1529,6 +1535,45 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); } + } else if (cmd_frame[0] == CMD_GET_STATS_CORE) { + char json_reply[160]; + formatStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_CORE; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } + } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { + char json_reply[160]; + formatRadioStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_RADIO; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } + } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { + char json_reply[160]; + formatPacketStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_PACKETS; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { @@ -1565,6 +1610,21 @@ void MyMesh::enterCLIRescue() { Serial.println("========= CLI Rescue ========="); } +void MyMesh::formatStatsReply(char *reply) { + // Use StatsFormatHelper + // Note: err_flags is private in Dispatcher, so we use 0 + StatsFormatHelper::formatCoreStats(reply, board, *_ms_clock, 0, _pkt_mgr); +} + +void MyMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void MyMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + void MyMesh::checkCLIRescueCmd() { int len = strlen(cli_command); while (Serial.available() && len < sizeof(cli_command)-1) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index f2b56e5e3a..ef451d5ddd 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -69,6 +69,7 @@ #include #include +#include /* -------------------------------------------------------------------------------------- */ @@ -170,6 +171,11 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { void checkCLIRescueCmd(); void checkSerialInterface(); + // Stats methods + void formatStatsReply(char *reply); + void formatRadioStatsReply(char *reply); + void formatPacketStatsReply(char *reply); + // helpers, short-cuts void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } @@ -178,6 +184,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { private: DataStore* _store; NodePrefs _prefs; + mesh::PacketManager* _pkt_mgr; // stored for stats access + mesh::MillisecondClock* _ms_clock; // stored for stats access uint32_t pending_login; uint32_t pending_status; uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ From c9aa536ca632db7f28029ba1a4a66bc4bdccda92 Mon Sep 17 00:00:00 2001 From: agessaman Date: Sat, 8 Nov 2025 20:44:42 -0800 Subject: [PATCH 107/409] Reverted MyMesh constructor for simplicity. Updated formatStatsReply method to use new member variables for statistics handling. Removed excess variable creation --- examples/companion_radio/MyMesh.cpp | 5 ++--- examples/companion_radio/MyMesh.h | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index fa9edc8c26..6fe165c578 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -710,7 +710,7 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui) - : BaseChatMesh(radio, *(_ms_clock = new ArduinoMillis()), rng, rtc, *(_pkt_mgr = new StaticPoolPacketManager(16)), tables), + : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) { _iter_started = false; _cli_rescue = false; @@ -1612,8 +1612,7 @@ void MyMesh::enterCLIRescue() { void MyMesh::formatStatsReply(char *reply) { // Use StatsFormatHelper - // Note: err_flags is private in Dispatcher, so we use 0 - StatsFormatHelper::formatCoreStats(reply, board, *_ms_clock, 0, _pkt_mgr); + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); } void MyMesh::formatRadioStatsReply(char *reply) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index ef451d5ddd..61a25e14f2 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -184,8 +184,6 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { private: DataStore* _store; NodePrefs _prefs; - mesh::PacketManager* _pkt_mgr; // stored for stats access - mesh::MillisecondClock* _ms_clock; // stored for stats access uint32_t pending_login; uint32_t pending_status; uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ From 80d6dd4367c2882a5b576ed1394bb4c2151b290e Mon Sep 17 00:00:00 2001 From: agessaman Date: Sun, 9 Nov 2025 11:28:11 -0800 Subject: [PATCH 108/409] Update statistics handling to use binary frames instead of JSON formatting for consistency with other companion commands. Added documentation of frame structure with code examples. --- docs/stats_binary_frames.md | 225 ++++++++++++++++++++++++++++ examples/companion_radio/MyMesh.cpp | 62 ++++---- 2 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 docs/stats_binary_frames.md diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md new file mode 100644 index 0000000000..d4327ea6e5 --- /dev/null +++ b/docs/stats_binary_frames.md @@ -0,0 +1,225 @@ +# Stats Binary Frame Structures + +Binary frame structures for companion radio stats commands. All multi-byte integers use little-endian byte order. + +## Command Codes + +| Command | Code | Description | +|---------|------|-------------| +| `CMD_GET_STATS_CORE` | 56 | Get core device statistics | +| `CMD_GET_STATS_RADIO` | 57 | Get radio statistics | +| `CMD_GET_STATS_PACKETS` | 58 | Get packet statistics | + +## Response Codes + +| Response | Code | Description | +|----------|------|-------------| +| `RESP_CODE_STATS_CORE` | 24 | Core stats response | +| `RESP_CODE_STATS_RADIO` | 25 | Radio stats response | +| `RESP_CODE_STATS_PACKETS` | 26 | Packet stats response | + +--- + +## RESP_CODE_STATS_CORE (24) + +**Total Frame Size:** 10 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | +| 3 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | +| 7 | 2 | uint16_t | errors | Error flags bitmask | - | +| 9 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | + +### Example Structure (C/C++) + +```c +struct StatsCore { + uint8_t response_code; // 0x18 + uint16_t battery_mv; + uint32_t uptime_secs; + uint16_t errors; + uint8_t queue_len; +} __attribute__((packed)); +``` + +--- + +## RESP_CODE_STATS_RADIO (25) + +**Total Frame Size:** 13 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x19` (25) | - | +| 1 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | +| 3 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | +| 4 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | +| 5 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | +| 9 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | + +### Example Structure (C/C++) + +```c +struct StatsRadio { + uint8_t response_code; // 0x19 + int16_t noise_floor; + int8_t last_rssi; + int8_t last_snr; // Divide by 4.0 to get actual SNR in dB + uint32_t tx_air_secs; + uint32_t rx_air_secs; +} __attribute__((packed)); +``` + +--- + +## RESP_CODE_STATS_PACKETS (26) + +**Total Frame Size:** 25 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x1A` (26) | - | +| 1 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | +| 5 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | +| 9 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | +| 13 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | +| 17 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | +| 21 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | + +### Notes + +- Counters are cumulative from boot and may wrap. +- `recv = flood_rx + direct_rx` +- `sent = flood_tx + direct_tx` + +### Example Structure (C/C++) + +```c +struct StatsPackets { + uint8_t response_code; // 0x1A + uint32_t recv; + uint32_t sent; + uint32_t flood_tx; + uint32_t direct_tx; + uint32_t flood_rx; + uint32_t direct_rx; +} __attribute__((packed)); +``` + +--- + +## Usage Example (Python) + +```python +import struct + +def parse_stats_core(frame): + """Parse RESP_CODE_STATS_CORE frame (10 bytes)""" + response_code, battery_mv, uptime_secs, errors, queue_len = \ + struct.unpack('writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + uint16_t battery_mv = board.getBattMilliVolts(); + uint32_t uptime_secs = _ms->getMillis() / 1000; + uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); + memcpy(&out_frame[i], &battery_mv, 2); i += 2; + memcpy(&out_frame[i], &uptime_secs, 4); i += 4; + memcpy(&out_frame[i], &_err_flags, 2); i += 2; + out_frame[i++] = queue_len; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { - char json_reply[160]; - formatRadioStatsReply(json_reply); int i = 0; out_frame[i++] = RESP_CODE_STATS_RADIO; - int json_len = strlen(json_reply); - if (i + json_len <= MAX_FRAME_SIZE) { - memcpy(&out_frame[i], json_reply, json_len); - i += json_len; - _serial->writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); + int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); + int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision + uint32_t tx_air_secs = getTotalAirTime() / 1000; + uint32_t rx_air_secs = getReceiveAirTime() / 1000; + memcpy(&out_frame[i], &noise_floor, 2); i += 2; + out_frame[i++] = last_rssi; + out_frame[i++] = last_snr; + memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; + memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { - char json_reply[160]; - formatPacketStatsReply(json_reply); int i = 0; out_frame[i++] = RESP_CODE_STATS_PACKETS; - int json_len = strlen(json_reply); - if (i + json_len <= MAX_FRAME_SIZE) { - memcpy(&out_frame[i], json_reply, json_len); - i += json_len; - _serial->writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + uint32_t recv = radio_driver.getPacketsRecv(); + uint32_t sent = radio_driver.getPacketsSent(); + uint32_t n_sent_flood = getNumSentFlood(); + uint32_t n_sent_direct = getNumSentDirect(); + uint32_t n_recv_flood = getNumRecvFlood(); + uint32_t n_recv_direct = getNumRecvDirect(); + memcpy(&out_frame[i], &recv, 4); i += 4; + memcpy(&out_frame[i], &sent, 4); i += 4; + memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; + memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; + memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; + memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { From 39f83efbfe9c34129996f35eec6de916b890762c Mon Sep 17 00:00:00 2001 From: agessaman Date: Sun, 9 Nov 2025 11:39:47 -0800 Subject: [PATCH 109/409] Remove unused statistics formatting methods and associated header includes from MyMesh class. Whoops. --- examples/companion_radio/MyMesh.cpp | 14 -------------- examples/companion_radio/MyMesh.h | 6 ------ 2 files changed, 20 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6fc9a433bb..140f58c6ae 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1612,20 +1612,6 @@ void MyMesh::enterCLIRescue() { Serial.println("========= CLI Rescue ========="); } -void MyMesh::formatStatsReply(char *reply) { - // Use StatsFormatHelper - StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); -} - -void MyMesh::formatRadioStatsReply(char *reply) { - StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); -} - -void MyMesh::formatPacketStatsReply(char *reply) { - StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), - getNumRecvFlood(), getNumRecvDirect()); -} - void MyMesh::checkCLIRescueCmd() { int len = strlen(cli_command); while (Serial.available() && len < sizeof(cli_command)-1) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 61a25e14f2..f2b56e5e3a 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -69,7 +69,6 @@ #include #include -#include /* -------------------------------------------------------------------------------------- */ @@ -171,11 +170,6 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { void checkCLIRescueCmd(); void checkSerialInterface(); - // Stats methods - void formatStatsReply(char *reply); - void formatRadioStatsReply(char *reply); - void formatPacketStatsReply(char *reply); - // helpers, short-cuts void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } From b59d1999e6f6d79aa673e136cc52319229444bc0 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 11 Nov 2025 20:08:05 +1100 Subject: [PATCH 110/409] * Sensor: DISCOVER_REQ, prefix_only support --- examples/simple_sensor/SensorMesh.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 2ab2081f04..20d9921b95 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -636,14 +636,15 @@ void SensorMesh::onControlDataRecv(mesh::Packet* packet) { } if ((filter & (1 << ADV_TYPE_SENSOR)) != 0 && _prefs.discovery_mod_timestamp >= since) { + bool prefix_only = packet->payload[0] & 1; uint8_t data[6 + PUB_KEY_SIZE]; data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_SENSOR; // low 4-bits for node type data[1] = packet->_snr; // let sender know the inbound SNR ( x 4) memcpy(&data[2], &tag, 4); // include tag from request, for client to match to memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); - auto resp = createControlData(data, sizeof(data)); + auto resp = createControlData(data, prefix_only ? 6 + 8 : 6 + PUB_KEY_SIZE); if (resp) { - sendZeroHop(resp, getRetransmitDelay(resp)); // apply random delay, as multiple nodes can respond to this + sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this } } } From b0ce00652f45057493eb9893ec549e1bfaa5fbca Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:00:27 +0100 Subject: [PATCH 111/409] Fix RAK4631 GPS UART pin macros --- variants/rak4631/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 9a0504dcc4..7db67abffc 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -9,8 +9,8 @@ build_flags = ${nrf52_base.build_flags} -D RAK_BOARD -D PIN_BOARD_SCL=14 -D PIN_BOARD_SDA=13 - -D PIN_GPS_TX=16 - -D PIN_GPS_RX=15 + -D PIN_GPS_TX=PIN_SERIAL1_RX + -D PIN_GPS_RX=PIN_SERIAL1_TX -D PIN_GPS_EN=-1 -D PIN_OLED_RESET=-1 -D RADIO_CLASS=CustomSX1262 From 750e955f193780b8eb1065b21691eb7e704b4377 Mon Sep 17 00:00:00 2001 From: fdlamotte Date: Thu, 13 Nov 2025 10:39:20 +0100 Subject: [PATCH 112/409] Update library.json to latest libs and version --- library.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library.json b/library.json index 572c55b000..aa37cb6ed0 100644 --- a/library.json +++ b/library.json @@ -1,14 +1,14 @@ { "name": "MeshCore", - "version" : "1.8.0", + "version" : "1.10.0", "dependencies": { "SPI": "*", "Wire": "*", - "jgromes/RadioLib": "^7.1.2", + "jgromes/RadioLib": "^7.3.0", "rweather/Crypto": "^0.4.0", "adafruit/RTClib": "^2.1.3", "melopero/Melopero RV3028": "^1.1.0", - "electroniccats/CayenneLPP": "1.4.0" + "electroniccats/CayenneLPP": "1.6.1" }, "build": { "extraScript": "build_as_lib.py" From 91e9fcea4b76307d0e109730e848ddb819df9360 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 13 Nov 2025 20:45:38 +1100 Subject: [PATCH 113/409] * ver 1.10.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index f2b56e5e3a..927ec65eb5 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "2 Oct 2025" +#define FIRMWARE_BUILD_DATE "13 Nov 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.9.1" +#define FIRMWARE_VERSION "v1.10.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 00720bf707..d8a20486a5 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -68,11 +68,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "2 Oct 2025" + #define FIRMWARE_BUILD_DATE "13 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.1" + #define FIRMWARE_VERSION "v1.10.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index f0dec42b12..8641caaf97 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "2 Oct 2025" + #define FIRMWARE_BUILD_DATE "13 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.1" + #define FIRMWARE_VERSION "v1.10.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index cc14f59693..00d9c698a2 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "2 Oct 2025" + #define FIRMWARE_BUILD_DATE "13 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.1" + #define FIRMWARE_VERSION "v1.10.0" #endif #define FIRMWARE_ROLE "sensor" From 16c294ce6029a0a924762bf8825f8543e1703834 Mon Sep 17 00:00:00 2001 From: Stephan Rodemeier Date: Thu, 13 Nov 2025 22:25:55 +0100 Subject: [PATCH 114/409] Allow SF smaller than 7 to be saved --- examples/companion_radio/MyMesh.cpp | 2 +- src/helpers/CommonCLI.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 598d535f77..c0075a225d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1128,7 +1128,7 @@ void MyMesh::handleCmdFrame(size_t len) { uint8_t sf = cmd_frame[i++]; uint8_t cr = cmd_frame[i++]; - if (freq >= 300000 && freq <= 2500000 && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && + if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && bw <= 500000) { _prefs.sf = sf; _prefs.cr = cr; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index fc9fd5eb17..93773cceec 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -233,7 +233,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { + if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { _callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins); sprintf(reply, "OK - temp params for %d mins", temp_timeout_mins); } else { @@ -411,7 +411,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch float bw = num > 1 ? atof(parts[1]) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { + if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { _prefs->sf = sf; _prefs->cr = cr; _prefs->freq = freq; From 850d57a8f243dce0193008ee46b2f276d4fa5919 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 14 Nov 2025 21:51:28 -0700 Subject: [PATCH 115/409] Refactor float conversion in CommonCLI to use strtof for improved precision and add ftoa3 function for formatting floats with three decimal places in TxtDataHelpers to fix display issue in app and repeater config ui in web REPO: 1. Flash a repeater 2. Connect over lora 3. Set bw to 42.7 KHZ It will revert back due to converting a double to a float. REPO2: 1.Flash Repeater 2. Apply float fix 3. Set to say 20.8 4. try to get value via app or web cli repeater config It wil show blank because it doesnt return a good value. It would be something like 20.7999992 which the app and web apps dont like so the ftoa3 rounds it and returns a 3 decimal point float --- src/helpers/CommonCLI.cpp | 10 +++++----- src/helpers/TxtDataHelpers.cpp | 13 +++++++++++++ src/helpers/TxtDataHelpers.h | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 93773cceec..481d9ca787 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -228,8 +228,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(tmp, &command[10]); const char *parts[5]; int num = mesh::Utils::parseTextParts(tmp, parts, 5); - float freq = num > 0 ? atof(parts[0]) : 0.0f; - float bw = num > 1 ? atof(parts[1]) : 0.0f; + float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; + float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0; @@ -284,7 +284,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "radio", 5) == 0) { char freq[16], bw[16]; strcpy(freq, StrHelper::ftoa(_prefs->freq)); - strcpy(bw, StrHelper::ftoa(_prefs->bw)); + strcpy(bw, StrHelper::ftoa3(_prefs->bw)); sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr); } else if (memcmp(config, "rxdelay", 7) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base)); @@ -407,8 +407,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(tmp, &config[6]); const char *parts[4]; int num = mesh::Utils::parseTextParts(tmp, parts, 4); - float freq = num > 0 ? atof(parts[0]) : 0.0f; - float bw = num > 1 ? atof(parts[1]) : 0.0f; + float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; + float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index 09e86c676e..d327931fde 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -140,6 +140,19 @@ const char* StrHelper::ftoa(float f) { return tmp; } +const char* StrHelper::ftoa3(float f) { + static char s[16]; + int v = (int)(f * 1000.0f + (f >= 0 ? 0.5f : -0.5f)); // rounded ×1000 + int w = v / 1000; // whole + int d = abs(v % 1000); // decimals + snprintf(s, sizeof(s), "%d.%03d", w, d); + for (int i = strlen(s) - 1; i > 0 && s[i] == '0'; i--) + s[i] = 0; + int L = strlen(s); + if (s[L - 1] == '.') s[L - 1] = 0; + return s; +} + uint32_t StrHelper::fromHex(const char* src) { uint32_t n = 0; while (*src) { diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 387e09b931..6ab84d3975 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -12,6 +12,7 @@ class StrHelper { static void strncpy(char* dest, const char* src, size_t buf_sz); static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls static const char* ftoa(float f); + static const char* ftoa3(float f); //Converts float to string with 3 decimal places static bool isBlank(const char* str); static uint32_t fromHex(const char* src); }; From 2058af8453540ff864e414df774509c443f4971d Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 16 Nov 2025 15:55:03 +1100 Subject: [PATCH 116/409] initial support: Keepteen LT1 --- boards/keepteen_lt1.json | 79 ++++++++++++ variants/keepteen_lt1/KeepteenLT1Board.cpp | 75 ++++++++++++ variants/keepteen_lt1/KeepteenLT1Board.h | 51 ++++++++ variants/keepteen_lt1/platformio.ini | 132 +++++++++++++++++++++ variants/keepteen_lt1/target.cpp | 51 ++++++++ variants/keepteen_lt1/target.h | 30 +++++ variants/keepteen_lt1/variant.cpp | 22 ++++ variants/keepteen_lt1/variant.h | 74 ++++++++++++ 8 files changed, 514 insertions(+) create mode 100644 boards/keepteen_lt1.json create mode 100644 variants/keepteen_lt1/KeepteenLT1Board.cpp create mode 100644 variants/keepteen_lt1/KeepteenLT1Board.h create mode 100644 variants/keepteen_lt1/platformio.ini create mode 100644 variants/keepteen_lt1/target.cpp create mode 100644 variants/keepteen_lt1/target.h create mode 100644 variants/keepteen_lt1/variant.cpp create mode 100644 variants/keepteen_lt1/variant.h diff --git a/boards/keepteen_lt1.json b/boards/keepteen_lt1.json new file mode 100644 index 0000000000..c23b0b8803 --- /dev/null +++ b/boards/keepteen_lt1.json @@ -0,0 +1,79 @@ +{ + "build": { + "arduino":{ + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x00B3" + ], + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "Keepteen LT1", + "mcu": "nrf52840", + "variant": "Keepteen LT1", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino", + "zephyr" + ], + "name": "Keepteen LT1", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "http://www.keepteen.com/", + "vendor": "Keepteen" + } \ No newline at end of file diff --git a/variants/keepteen_lt1/KeepteenLT1Board.cpp b/variants/keepteen_lt1/KeepteenLT1Board.cpp new file mode 100644 index 0000000000..46bff1fc2e --- /dev/null +++ b/variants/keepteen_lt1/KeepteenLT1Board.cpp @@ -0,0 +1,75 @@ +#include +#include "KeepteenLT1Board.h" + +#include +#include + +static BLEDfu bledfu; + +void KeepteenLT1Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + btn_prev_state = HIGH; + + pinMode(PIN_VBAT_READ, INPUT); + + #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); + #endif + + Wire.begin(); +} + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +bool KeepteenLT1Board::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("KeepteenLT1_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h new file mode 100644 index 0000000000..9892638b28 --- /dev/null +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +class KeepteenLT1Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + uint8_t btn_prev_state; + +public: + void begin(); + + uint8_t getStartupReason() const override { return startup_reason; } + + #define BATTERY_SAMPLES 8 + + uint16_t getBattMilliVolts() override { + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + return (ADC_MULTIPLIER * raw); + } + + const char* getManufacturerName() const override { + return "Keepteen LT1"; + } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } +#endif + + void reboot() override { + NVIC_SystemReset(); + } + + void powerOff() override { + sd_power_system_off(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini new file mode 100644 index 0000000000..96a55eb76a --- /dev/null +++ b/variants/keepteen_lt1/platformio.ini @@ -0,0 +1,132 @@ +[KeepteenLT1] +extends = nrf52_base +board = keepteen_lt1 +build_flags = ${nrf52_base.build_flags} + -I variants/keepteen_lt1 + -D KEEPTEEN_LT1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=34 + -D PIN_BOARD_SCL=36 + -D ENV_INCLUDE_GPS=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/keepteen_lt1> +lib_deps= ${nrf52_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + stevemarple/MicroNMEA @ ^2.0.6 + +[env:KeepteenLT1_repeater] +extends = KeepteenLT1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_repeater> + + + + +build_flags = + ${KeepteenLT1.build_flags} + -D ADVERT_NAME='"KeepteenLT1 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_room_server] +extends = KeepteenLT1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_room_server> + + + + +build_flags = ${KeepteenLT1.build_flags} + -D ADVERT_NAME='"KeepteenLT1 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_terminal_chat] +extends = KeepteenLT1 +build_flags = ${KeepteenLT1.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_companion_radio_usb] +extends = KeepteenLT1 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = ${KeepteenLT1.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 + +[env:KeepteenLT1_companion_radio_ble] +extends = KeepteenLT1 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = ${KeepteenLT1.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 + +; [env:KeepteenLT1_sensor] +; extends = KeepteenLT1 +; build_flags = +; ${KeepteenLT1.build_flags} +; -D ADVERT_NAME='"KeepteenLT1 Sensor"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D DISPLAY_CLASS=SSD1306Display +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${KeepteenLT1.build_src_filter} +; + +; + +; +<../examples/simple_sensor> +; lib_deps = +; ${KeepteenLT1.lib_deps} diff --git a/variants/keepteen_lt1/target.cpp b/variants/keepteen_lt1/target.cpp new file mode 100644 index 0000000000..e72abf08b7 --- /dev/null +++ b/variants/keepteen_lt1/target.cpp @@ -0,0 +1,51 @@ +#include +#include "target.h" +#include + +KeepteenLT1Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/keepteen_lt1/target.h b/variants/keepteen_lt1/target.h new file mode 100644 index 0000000000..0f1aa756c1 --- /dev/null +++ b/variants/keepteen_lt1/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +#include + +extern KeepteenLT1Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/keepteen_lt1/variant.cpp b/variants/keepteen_lt1/variant.cpp new file mode 100644 index 0000000000..47a20f13b7 --- /dev/null +++ b/variants/keepteen_lt1/variant.cpp @@ -0,0 +1,22 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() +{ + // set LED pin as output and set it low for off + pinMode(PIN_LED, OUTPUT); + digitalWrite(PIN_LED, LOW); + + // set INPUT_PULLUP for user button + pinMode(PIN_USER_BTN, INPUT_PULLUP); + +} + diff --git a/variants/keepteen_lt1/variant.h b/variants/keepteen_lt1/variant.h new file mode 100644 index 0000000000..a2b63fadc8 --- /dev/null +++ b/variants/keepteen_lt1/variant.h @@ -0,0 +1,74 @@ +#ifndef _KEEPTEEN_LT1_H_ +#define _KEEPTEEN_LT1_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) +#define USE_LFRC + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (15) // Blue LED +#define PIN_LED2 (13) // Maybe red power LED? +#define LED_BLUE (-1) // Disable annoying flashing caused by Bluefruit +#define LED_BUILTIN PIN_LED +#define P_LORA_TX_LED PIN_LED +#define LED_STATE_ON 1 + +// Buttons +#define PIN_BUTTON1 (32) // Menu / User Button +#define PIN_USER_BTN PIN_BUTTON1 + +// Analog pins +#define PIN_VBAT_READ (31) +#define AREF_VOLTAGE (3.6F) +#define ADC_MULTIPLIER (1.535F) +#define ADC_RESOLUTION (12) + +// Serial interfaces +#define PIN_SERIAL1_RX (22) +#define PIN_SERIAL1_TX (20) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (2) +#define PIN_SPI_MOSI (38) +#define PIN_SPI_SCK (43) + +// Lora Pins +#define P_LORA_BUSY (29) +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI +#define P_LORA_NSS (45) +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_DIO_1 (10) +#define P_LORA_RESET (9) +#define SX126X_RXEN RADIOLIB_NC +#define SX126X_TXEN RADIOLIB_NC +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (34) +#define PIN_WIRE_SCL (36) +#define I2C_NO_RESCAN + +// GPS L76KB +#define GPS_BAUDRATE 9600 +#define PIN_GPS_TX PIN_SERIAL1_RX +#define PIN_GPS_RX PIN_SERIAL1_TX +#define PIN_GPS_EN (24) + +#endif // _KEEPTEEN_LT1_H_ \ No newline at end of file From bc2256f232c476ada4d4a1bee3863c25ce05f02f Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 16 Nov 2025 16:17:11 +1100 Subject: [PATCH 117/409] Keepteen LT1: remove terminal_chat and sensor targets --- variants/keepteen_lt1/platformio.ini | 33 +--------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini index 96a55eb76a..cb3ea9c88b 100644 --- a/variants/keepteen_lt1/platformio.ini +++ b/variants/keepteen_lt1/platformio.ini @@ -56,19 +56,6 @@ build_flags = ${KeepteenLT1.build_flags} lib_deps = ${KeepteenLT1.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:KeepteenLT1_terminal_chat] -extends = KeepteenLT1 -build_flags = ${KeepteenLT1.build_flags} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${KeepteenLT1.build_src_filter} - +<../examples/simple_secure_chat/main.cpp> -lib_deps = ${KeepteenLT1.lib_deps} - densaugeo/base64 @ ~1.4.0 - adafruit/RTClib @ ^2.1.3 - [env:KeepteenLT1_companion_radio_usb] extends = KeepteenLT1 board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld @@ -111,22 +98,4 @@ build_src_filter = ${KeepteenLT1.build_src_filter} +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${KeepteenLT1.lib_deps} adafruit/RTClib @ ^2.1.3 - densaugeo/base64 @ ~1.4.0 - -; [env:KeepteenLT1_sensor] -; extends = KeepteenLT1 -; build_flags = -; ${KeepteenLT1.build_flags} -; -D ADVERT_NAME='"KeepteenLT1 Sensor"' -; -D ADVERT_LAT=0.0 -; -D ADVERT_LON=0.0 -; -D ADMIN_PASSWORD='"password"' -; -D DISPLAY_CLASS=SSD1306Display -; ; -D MESH_PACKET_LOGGING=1 -; ; -D MESH_DEBUG=1 -; build_src_filter = ${KeepteenLT1.build_src_filter} -; + -; + -; +<../examples/simple_sensor> -; lib_deps = -; ${KeepteenLT1.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file From 3dd6dc02ea49eef565b58c34c21399e9dcc9beae Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 16 Nov 2025 16:55:16 +0100 Subject: [PATCH 118/409] xiao_s3: use environment sensor manager and add sensor role --- variants/xiao_s3_wio/platformio.ini | 24 ++++++++++++++++++++++++ variants/xiao_s3_wio/target.cpp | 2 +- variants/xiao_s3_wio/target.h | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 95a54012db..04a14db230 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -4,7 +4,9 @@ board = seeed_xiao_esp32s3 board_check = true board_build.mcu = esp32s3 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} -I variants/xiao_s3_wio + -UENV_INCLUDE_GPS -D SEEED_XIAO_S3 -D P_LORA_DIO_1=39 -D P_LORA_NSS=41 @@ -15,6 +17,8 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MOSI=9 -D PIN_USER_BTN=21 -D PIN_STATUS_LED=48 + -D PIN_BOARD_SDA=D4 + -D PIN_BOARD_SCL=D5 -D SX126X_RXEN=38 -D SX126X_TXEN=RADIOLIB_NC -D SX126X_DIO2_AS_RF_SWITCH=true @@ -26,6 +30,10 @@ build_flags = ${esp32_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_s3_wio> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} [env:Xiao_S3_WIO_repeater] extends = Xiao_S3_WIO @@ -185,3 +193,19 @@ lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:Xiao_S3_WIO_sensor] +extends = Xiao_S3_WIO +build_src_filter = ${Xiao_S3_WIO.build_src_filter} + +<../examples/simple_sensor> +build_flags = + ${Xiao_S3_WIO.build_flags} + -D ADVERT_NAME='"XiaoS3 sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_S3_WIO.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 41f25da6a1..26cd27ac2a 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -14,7 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index b84ab42b09..c3227368ca 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #ifdef DISPLAY_CLASS #include #include @@ -16,7 +16,7 @@ extern XiaoS3WIOBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 838e83b3b54790e91bf180ccdae669b13092ea59 Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 16 Nov 2025 17:07:33 +0100 Subject: [PATCH 119/409] xiao_s3: relocate serial pins on repeater_bridge_rs232 * conflicts with i2c pins that are documented on the same pins * this is a commented target --- variants/xiao_s3_wio/platformio.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 04a14db230..8979edc25a 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -65,8 +65,9 @@ lib_deps = ; -D ADMIN_PASSWORD='"password"' ; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 -; -D WITH_RS232_BRIDGE_RX=5 -; -D WITH_RS232_BRIDGE_TX=6 +; RS232 bridge Pins have been relocated from 5,6 which is the i2c bus on xiao_s3 +; -D WITH_RS232_BRIDGE_RX=3 +; -D WITH_RS232_BRIDGE_TX=4 ; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 From a3c9a07377d1895f78c776049ebd2bd99b90f4ae Mon Sep 17 00:00:00 2001 From: agessaman Date: Mon, 17 Nov 2025 09:57:36 -0800 Subject: [PATCH 120/409] Modify CMD_GET_STATS with sub-types for core, radio, and packet statistics. Consolidated to a single RESP_CODE_STATS with a second byte to identify response structure. Updated documentation and examples to reflect the new command structure and response parsing. --- docs/stats_binary_frames.md | 201 ++++++++++++++++++++-------- examples/companion_radio/MyMesh.cpp | 103 +++++++------- 2 files changed, 200 insertions(+), 104 deletions(-) diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md index d4327ea6e5..1b409912ee 100644 --- a/docs/stats_binary_frames.md +++ b/docs/stats_binary_frames.md @@ -6,37 +6,53 @@ Binary frame structures for companion radio stats commands. All multi-byte integ | Command | Code | Description | |---------|------|-------------| -| `CMD_GET_STATS_CORE` | 56 | Get core device statistics | -| `CMD_GET_STATS_RADIO` | 57 | Get radio statistics | -| `CMD_GET_STATS_PACKETS` | 58 | Get packet statistics | +| `CMD_GET_STATS` | 56 | Get statistics (2-byte command: code + sub-type) | + +### Stats Sub-Types + +The `CMD_GET_STATS` command uses a 2-byte frame structure: +- **Byte 0:** `CMD_GET_STATS` (56) +- **Byte 1:** Stats sub-type: + - `STATS_TYPE_CORE` (0) - Get core device statistics + - `STATS_TYPE_RADIO` (1) - Get radio statistics + - `STATS_TYPE_PACKETS` (2) - Get packet statistics ## Response Codes | Response | Code | Description | |----------|------|-------------| -| `RESP_CODE_STATS_CORE` | 24 | Core stats response | -| `RESP_CODE_STATS_RADIO` | 25 | Radio stats response | -| `RESP_CODE_STATS_PACKETS` | 26 | Packet stats response | +| `RESP_CODE_STATS` | 24 | Statistics response (2-byte response: code + sub-type) | + +### Stats Response Sub-Types + +The `RESP_CODE_STATS` response uses a 2-byte header structure: +- **Byte 0:** `RESP_CODE_STATS` (24) +- **Byte 1:** Stats sub-type (matches command sub-type): + - `STATS_TYPE_CORE` (0) - Core device statistics response + - `STATS_TYPE_RADIO` (1) - Radio statistics response + - `STATS_TYPE_PACKETS` (2) - Packet statistics response --- -## RESP_CODE_STATS_CORE (24) +## RESP_CODE_STATS + STATS_TYPE_CORE (24, 0) -**Total Frame Size:** 10 bytes +**Total Frame Size:** 11 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| | 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | -| 1 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | -| 3 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | -| 7 | 2 | uint16_t | errors | Error flags bitmask | - | -| 9 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | +| 1 | 1 | uint8_t | stats_type | Always `0x00` (STATS_TYPE_CORE) | - | +| 2 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | +| 4 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | +| 8 | 2 | uint16_t | errors | Error flags bitmask | - | +| 10 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | ### Example Structure (C/C++) ```c struct StatsCore { uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x00 (STATS_TYPE_CORE) uint16_t battery_mv; uint32_t uptime_secs; uint16_t errors; @@ -46,24 +62,26 @@ struct StatsCore { --- -## RESP_CODE_STATS_RADIO (25) +## RESP_CODE_STATS + STATS_TYPE_RADIO (24, 1) -**Total Frame Size:** 13 bytes +**Total Frame Size:** 14 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| -| 0 | 1 | uint8_t | response_code | Always `0x19` (25) | - | -| 1 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | -| 3 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | -| 4 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | -| 5 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | -| 9 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 1 | uint8_t | stats_type | Always `0x01` (STATS_TYPE_RADIO) | - | +| 2 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | +| 4 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | +| 5 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | +| 6 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | +| 10 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | ### Example Structure (C/C++) ```c struct StatsRadio { - uint8_t response_code; // 0x19 + uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x01 (STATS_TYPE_RADIO) int16_t noise_floor; int8_t last_rssi; int8_t last_snr; // Divide by 4.0 to get actual SNR in dB @@ -74,19 +92,20 @@ struct StatsRadio { --- -## RESP_CODE_STATS_PACKETS (26) +## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2) -**Total Frame Size:** 25 bytes +**Total Frame Size:** 26 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| -| 0 | 1 | uint8_t | response_code | Always `0x1A` (26) | - | -| 1 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | -| 5 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | -| 9 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | -| 13 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | -| 17 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | -| 21 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 1 | uint8_t | stats_type | Always `0x02` (STATS_TYPE_PACKETS) | - | +| 2 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | +| 6 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | +| 10 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | +| 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | +| 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | +| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | ### Notes @@ -98,7 +117,8 @@ struct StatsRadio { ```c struct StatsPackets { - uint8_t response_code; // 0x1A + uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x02 (STATS_TYPE_PACKETS) uint32_t recv; uint32_t sent; uint32_t flood_tx; @@ -110,15 +130,38 @@ struct StatsPackets { --- -## Usage Example (Python) +## Command Usage Example (Python) + +```python +# Send CMD_GET_STATS command +def send_get_stats_core(serial_interface): + """Send command to get core stats""" + cmd = bytes([56, 0]) # CMD_GET_STATS (56) + STATS_TYPE_CORE (0) + serial_interface.write(cmd) + +def send_get_stats_radio(serial_interface): + """Send command to get radio stats""" + cmd = bytes([56, 1]) # CMD_GET_STATS (56) + STATS_TYPE_RADIO (1) + serial_interface.write(cmd) + +def send_get_stats_packets(serial_interface): + """Send command to get packet stats""" + cmd = bytes([56, 2]) # CMD_GET_STATS (56) + STATS_TYPE_PACKETS (2) + serial_interface.write(cmd) +``` + +--- + +## Response Parsing Example (Python) ```python import struct def parse_stats_core(frame): - """Parse RESP_CODE_STATS_CORE frame (10 bytes)""" - response_code, battery_mv, uptime_secs, errors, queue_len = \ - struct.unpack('getMillis() / 1000; - uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); - memcpy(&out_frame[i], &battery_mv, 2); i += 2; - memcpy(&out_frame[i], &uptime_secs, 4); i += 4; - memcpy(&out_frame[i], &_err_flags, 2); i += 2; - out_frame[i++] = queue_len; - _serial->writeFrame(out_frame, i); - } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { - int i = 0; - out_frame[i++] = RESP_CODE_STATS_RADIO; - int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); - int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); - int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision - uint32_t tx_air_secs = getTotalAirTime() / 1000; - uint32_t rx_air_secs = getReceiveAirTime() / 1000; - memcpy(&out_frame[i], &noise_floor, 2); i += 2; - out_frame[i++] = last_rssi; - out_frame[i++] = last_snr; - memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; - memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; - _serial->writeFrame(out_frame, i); - } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { - int i = 0; - out_frame[i++] = RESP_CODE_STATS_PACKETS; - uint32_t recv = radio_driver.getPacketsRecv(); - uint32_t sent = radio_driver.getPacketsSent(); - uint32_t n_sent_flood = getNumSentFlood(); - uint32_t n_sent_direct = getNumSentDirect(); - uint32_t n_recv_flood = getNumRecvFlood(); - uint32_t n_recv_direct = getNumRecvDirect(); - memcpy(&out_frame[i], &recv, 4); i += 4; - memcpy(&out_frame[i], &sent, 4); i += 4; - memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; - memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; - memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; - memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; - _serial->writeFrame(out_frame, i); + } else if (cmd_frame[0] == CMD_GET_STATS && len >= 2) { + uint8_t stats_type = cmd_frame[1]; + if (stats_type == STATS_TYPE_CORE) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_CORE; + uint16_t battery_mv = board.getBattMilliVolts(); + uint32_t uptime_secs = _ms->getMillis() / 1000; + uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); + memcpy(&out_frame[i], &battery_mv, 2); i += 2; + memcpy(&out_frame[i], &uptime_secs, 4); i += 4; + memcpy(&out_frame[i], &_err_flags, 2); i += 2; + out_frame[i++] = queue_len; + _serial->writeFrame(out_frame, i); + } else if (stats_type == STATS_TYPE_RADIO) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_RADIO; + int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); + int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); + int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision + uint32_t tx_air_secs = getTotalAirTime() / 1000; + uint32_t rx_air_secs = getReceiveAirTime() / 1000; + memcpy(&out_frame[i], &noise_floor, 2); i += 2; + out_frame[i++] = last_rssi; + out_frame[i++] = last_snr; + memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; + memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; + _serial->writeFrame(out_frame, i); + } else if (stats_type == STATS_TYPE_PACKETS) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_PACKETS; + uint32_t recv = radio_driver.getPacketsRecv(); + uint32_t sent = radio_driver.getPacketsSent(); + uint32_t n_sent_flood = getNumSentFlood(); + uint32_t n_sent_direct = getNumSentDirect(); + uint32_t n_recv_flood = getNumRecvFlood(); + uint32_t n_recv_direct = getNumRecvDirect(); + memcpy(&out_frame[i], &recv, 4); i += 4; + memcpy(&out_frame[i], &sent, 4); i += 4; + memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; + memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; + memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; + memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type + } } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { From 88a614194324bc49b338beee61b96a16b0f5b1b3 Mon Sep 17 00:00:00 2001 From: recrof Date: Tue, 18 Nov 2025 15:36:25 +0100 Subject: [PATCH 121/409] fix: move bme680 detection before bme280 --- .../sensors/EnvironmentSensorManager.cpp | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 98339105bf..79dc87e5db 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -178,6 +178,16 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_BME680 + if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) { + MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); + BME680_initialized = true; + } else { + BME680_initialized = false; + MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); + } + #endif + #if ENV_INCLUDE_BME280 if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); @@ -301,16 +311,6 @@ bool EnvironmentSensorManager::begin() { } #endif - #if ENV_INCLUDE_BME680 - if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) { - MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); - BME680_initialized = true; - } else { - BME680_initialized = false; - MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); - } - #endif - #if ENV_INCLUDE_BMP085 // First argument is MODE (aka oversampling) // choose ULTRALOWPOWER @@ -344,6 +344,19 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_BME680 + if (BME680_initialized) { + if (BME680.performReading()) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); + telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); + next_available_channel++; + } + } + #endif + #if ENV_INCLUDE_BME280 if (BME280_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); @@ -452,19 +465,6 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif - #if ENV_INCLUDE_BME680 - if (BME680_initialized) { - if (BME680.performReading()) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); - telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); - telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); - next_available_channel++; - } - } - #endif - #if ENV_INCLUDE_BMP085 if (BMP085_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature()); @@ -563,7 +563,7 @@ void EnvironmentSensorManager::initBasicGPS() { gps_active = false; //Set GPS visibility off until setting is changed } -// gps code for rak might be moved to MicroNMEALoactionProvider +// gps code for rak might be moved to MicroNMEALoactionProvider // or make a new location provider ... #ifdef RAK_WISBLOCK_GPS void EnvironmentSensorManager::rakGPSInit(){ From 310618e6899337b37fb3a9268f8e6571bf1f760c Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 19 Nov 2025 11:43:52 +0800 Subject: [PATCH 122/409] add heltec_v4 tft expansion box --- src/helpers/ui/SSD1306Display.cpp | 14 +- src/helpers/ui/SSD1306Display.h | 9 +- src/helpers/ui/ST7789LCDDisplay.cpp | 5 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4/HeltecV4Board.cpp | 6 +- variants/heltec_v4/platformio.ini | 265 +++++++++++++++++++++++---- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 6 +- 8 files changed, 267 insertions(+), 44 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index c9da0cf8d5..4e7fd10ad0 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,6 +7,10 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } #ifdef DISPLAY_ROTATION display.setRotation(DISPLAY_ROTATION); #endif @@ -15,12 +19,18 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { display.ssd1306_command(SSD1306_DISPLAYON); - _isOn = true; + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); - _isOn = false; + if (_isOn) { + if (_peripher_power) _peripher_power->release(); + _isOn = false; + } } void SSD1306Display::clear() { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index 1a3a9602bb..d843da85b2 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -5,6 +5,7 @@ #include #define SSD1306_NO_SPLASH #include +#include #ifndef PIN_OLED_RESET #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) @@ -18,10 +19,16 @@ class SSD1306Display : public DisplayDriver { Adafruit_SSD1306 display; bool _isOn; uint8_t _color; + RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(128, 64, &Wire, PIN_OLED_RESET), + _peripher_power(peripher_power) + { + _isOn = false; + } bool begin(); bool isOn() override { return _isOn; } diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 87f9b8ad64..a686c0c8a6 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -25,10 +25,13 @@ bool ST7789LCDDisplay::begin() { pinMode(PIN_TFT_LEDA_CTL, OUTPUT); digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + pinMode(PIN_TFT_RST, OUTPUT); + digitalWrite(PIN_TFT_RST, LOW); + delay(10); digitalWrite(PIN_TFT_RST, HIGH); // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); #endif diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index a807714855..5b960ca198 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include class ST7789LCDDisplay : public DisplayDriver { - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ class ST7789LCDDisplay : public DisplayDriver { { _isOn = false; } -#elif LILYGO_TDECK +#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index f143db36f1..92f9343767 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -86,5 +86,9 @@ void HeltecV4Board::begin() { } const char* HeltecV4Board::getManufacturerName() const { - return "Heltec V4"; + #ifdef HELTEC_LORA_V4_TFT + return "Heltec V4 TFT"; + #else + return "Heltec V4 OLED"; + #endif } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c26a5bc69d..ba75900940 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,11 +20,9 @@ build_flags = -D P_LORA_PA_POWER=7 ;power en -D P_LORA_PA_EN=2 -D P_LORA_PA_TX_EN=46 ;enable tx - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VEXT_EN_ACTIVE=LOW -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true @@ -47,10 +45,44 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[env:heltec_v4_repeater] +[heltec_v4_oled] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_OLED + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D ENV_PIN_SDA=4 + -D ENV_PIN_SCL=3 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = ${Heltec_lora32_v4.lib_deps} + +[heltec_v4_tft] extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_TFT + -D PIN_BOARD_SDA=4 + -D PIN_BOARD_SCL=3 + -D DISPLAY_SCALE_X=2.5 + -D DISPLAY_SCALE_Y=3.75 + -D PIN_TFT_RST=18 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=21 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=15 + -D PIN_TFT_DC=16 + -D PIN_TFT_SCL=17 + -D PIN_TFT_SDA=33 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = + ${Heltec_lora32_v4.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:heltec_v4_repeater] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Repeater"' -D ADVERT_LAT=0.0 @@ -59,18 +91,18 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 [env:heltec_v4_repeater_bridge_espnow] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 @@ -81,18 +113,18 @@ build_flags = ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_room_server] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Room"' -D ADVERT_LAT=0.0 @@ -101,50 +133,50 @@ build_flags = -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_room_server> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_terminal_chat] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_usb] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_ble] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -155,20 +187,20 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_wifi] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -178,21 +210,21 @@ build_flags = -D WIFI_PWD='"mypwd"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_sensor] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} - -D ADVERT_NAME='"Heltec v3 Sensor"' + ${heltec_v4_oled.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -201,9 +233,172 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_sensor> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} + ${esp32_ota.lib_deps} + + +[env:heltec_v4_tft_repeater] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + + +[env:heltec_v4_tft_repeater_bridge_espnow] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_room_server] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_terminal_chat] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_usb] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_ble] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=ST7789LCDDisplay + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_wifi] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_sensor] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + -D DISPLAY_CLASS=ST7789LCDDisplay +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_sensor> +lib_deps = + ${heltec_v4_tft.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 015c3a8e76..0d2bd4976c 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display; + DISPLAY_CLASS display(&(board.periph_power)); MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index a153b2af35..00d2adab6d 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -9,7 +9,11 @@ #include #include #ifdef DISPLAY_CLASS - #include +#ifdef HELTEC_LORA_V4_OLED + #include +#elif defined(HELTEC_LORA_V4_TFT) + #include +#endif #include #endif From ed9655e14e40d9e4f5361631c1ddc0d3a70de999 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 12:48:33 +1100 Subject: [PATCH 123/409] rename faketec to promicro --- variants/promicro/platformio.ini | 64 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index b1c0c4eaee..90962d27c1 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -1,9 +1,9 @@ -[Faketec] +[Promicro] extends = nrf52_base board = promicro_nrf52840 build_flags = ${nrf52_base.build_flags} -I variants/promicro - -D FAKETEC + -D PROMICRO -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -34,14 +34,14 @@ lib_deps= ${nrf52_base.lib_deps} adafruit/Adafruit BMP280 Library@^2.6.8 stevemarple/MicroNMEA @ ^2.0.6 -[env:Faketec_repeater] -extends = Faketec -build_src_filter = ${Faketec.build_src_filter} +[env:ProMicro_repeater] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_repeater> + + build_flags = - ${Faketec.build_flags} + ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -50,16 +50,16 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:Faketec_room_server] -extends = Faketec -build_src_filter = ${Faketec.build_src_filter} +[env:ProMicro_room_server] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_room_server> + + -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -68,47 +68,47 @@ build_flags = ${Faketec.build_flags} -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:Faketec_terminal_chat] -extends = Faketec -build_flags = ${Faketec.build_flags} +[env:ProMicro_terminal_chat] +extends = Promicro +build_flags = ${Promicro.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_secure_chat/main.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 -[env:Faketec_companion_radio_usb] -extends = Faketec +[env:ProMicro_companion_radio_usb] +extends = Promicro board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld board_upload.maximum_size = 712704 -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 -[env:Faketec_companion_radio_ble] -extends = Faketec +[env:ProMicro_companion_radio_ble] +extends = Promicro board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld board_upload.maximum_size = 712704 -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -118,20 +118,20 @@ build_flags = ${Faketec.build_flags} -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 -[env:Faketec_sensor] -extends = Faketec +[env:ProMicro_sensor] +extends = Promicro build_flags = - ${Faketec.build_flags} + ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -139,9 +139,9 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} + + +<../examples/simple_sensor> lib_deps = - ${Faketec.lib_deps} + ${Promicro.lib_deps} From 2bd47de3b9824d99450baa4d1d90c9729c13032f Mon Sep 17 00:00:00 2001 From: zaquaz Date: Thu, 20 Nov 2025 18:55:39 -0800 Subject: [PATCH 124/409] Added buzzer config persistance accross restart --- examples/companion_radio/DataStore.cpp | 7 +++++++ examples/companion_radio/MyMesh.h | 5 +++-- examples/companion_radio/NodePrefs.h | 1 + examples/companion_radio/ui-new/UITask.cpp | 3 +++ examples/companion_radio/ui-orig/UITask.cpp | 3 +++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index eac027b89c..058389fe9a 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -200,6 +200,11 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; + + // Initialize defaults for any missing fields (backward compatibility) + memset(&_prefs, 0, sizeof(_prefs)); + node_lat = 0.0; + node_lon = 0.0; file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 @@ -221,6 +226,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 file.read(pad, 2); // 78 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.close(); } @@ -252,6 +258,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 file.write(pad, 2); // 78 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.close(); } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 927ec65eb5..9c22532d62 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -152,6 +152,9 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { pending_login = pending_status = pending_telemetry = pending_discovery = pending_req = 0; } +public: + void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } + private: void writeOKFrame(); void writeErrFrame(uint8_t err_code); @@ -171,11 +174,9 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { void checkSerialInterface(); // helpers, short-cuts - void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } void saveContacts() { _store->saveContacts(this); } -private: DataStore* _store; NodePrefs _prefs; uint32_t pending_login; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index bfde7218cf..13c9f88423 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -24,4 +24,5 @@ struct NodePrefs { // persisted to file float rx_delay_base; uint32_t ble_pin; uint8_t advert_loc_policy; + uint8_t buzzer_quiet; }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 086f825999..9213df12db 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -532,6 +532,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #ifdef PIN_BUZZER buzzer.begin(); + buzzer.quiet(_node_prefs->buzzer_quiet); #endif #ifdef PIN_VIBRATION @@ -871,6 +872,8 @@ void UITask::toggleBuzzer() { buzzer.quiet(true); showAlert("Buzzer: OFF", 800); } + _node_prefs->buzzer_quiet = buzzer.isQuiet(); + the_mesh.savePrefs(); _next_refresh = 0; // trigger refresh #endif } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d37..f7838d68a2 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -56,6 +56,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #ifdef PIN_BUZZER buzzer.begin(); + buzzer.quiet(_node_prefs->buzzer_quiet); #endif // Initialize digital button if available @@ -394,6 +395,8 @@ void UITask::handleButtonTriplePress() { buzzer.quiet(true); sprintf(_alert, "Buzzer: OFF"); } + _node_prefs->buzzer_quiet = buzzer.isQuiet(); + the_mesh.savePrefs(); _need_refresh = true; #endif } From b33d226c583bc601787454e512cca3f938200de4 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 21 Nov 2025 15:44:31 +1100 Subject: [PATCH 125/409] * proposal for 'Extended Trace' packets. Using 'flags' byte, lower 2 bits, for path hash size. --- examples/companion_radio/MyMesh.cpp | 50 ++++++++++++++++++----------- src/Identity.h | 3 ++ src/Mesh.cpp | 9 +++--- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c0075a225d..0afd11de8f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -670,6 +670,11 @@ void MyMesh::onRawDataRecv(mesh::Packet *packet) { void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags, const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) { + uint8_t path_sz = flags & 0x03; // NEW v1.11+ + if (12 + path_len + (path_len >> path_sz) + 1 > sizeof(out_frame)) { + MESH_DEBUG_PRINTLN("onTraceRecv(), path_len is too long: %d", (uint32_t)path_len); + return; + } int i = 0; out_frame[i++] = PUSH_CODE_TRACE_DATA; out_frame[i++] = 0; // reserved @@ -681,8 +686,9 @@ void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, i += 4; memcpy(&out_frame[i], path_hashes, path_len); i += path_len; - memcpy(&out_frame[i], path_snrs, path_len); - i += path_len; + + memcpy(&out_frame[i], path_snrs, path_len >> path_sz); + i += path_len >> path_sz; out_frame[i++] = (int8_t)(packet->getSNR() * 4); // extra/final SNR (to this node) if (_serial->isConnected()) { @@ -1446,25 +1452,31 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_BAD_STATE); } - } else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PATH_SIZE) { - uint32_t tag, auth; - memcpy(&tag, &cmd_frame[1], 4); - memcpy(&auth, &cmd_frame[5], 4); - auto pkt = createTrace(tag, auth, cmd_frame[9]); - if (pkt) { - uint8_t path_len = len - 10; - sendDirect(pkt, &cmd_frame[10], path_len); + } else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PACKET_PAYLOAD-5) { + uint8_t path_len = len - 10; + uint8_t flags = cmd_frame[9]; + uint8_t path_sz = flags & 0x03; // NEW v1.11+ + if ((path_len >> path_sz) > MAX_PATH_SIZE || (path_len % (1 << path_sz)) != 0) { // make sure is multiple of path_sz + writeErrFrame(ERR_CODE_ILLEGAL_ARG); + } else { + uint32_t tag, auth; + memcpy(&tag, &cmd_frame[1], 4); + memcpy(&auth, &cmd_frame[5], 4); + auto pkt = createTrace(tag, auth, flags); + if (pkt) { + sendDirect(pkt, &cmd_frame[10], path_len); - uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); - uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); + uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); + uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); - out_frame[0] = RESP_CODE_SENT; - out_frame[1] = 0; - memcpy(&out_frame[2], &tag, 4); - memcpy(&out_frame[6], &est_timeout, 4); - _serial->writeFrame(out_frame, 10); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); + out_frame[0] = RESP_CODE_SENT; + out_frame[1] = 0; + memcpy(&out_frame[2], &tag, 4); + memcpy(&out_frame[6], &est_timeout, 4); + _serial->writeFrame(out_frame, 10); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } } } else if (cmd_frame[0] == CMD_SET_DEVICE_PIN && len >= 5) { diff --git a/src/Identity.h b/src/Identity.h index 42fb9d9aed..60e8783b9b 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -23,6 +23,9 @@ class Identity { bool isHashMatch(const uint8_t* hash) const { return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0; } + bool isHashMatch(const uint8_t* hash, uint8_t len) const { + return memcmp(hash, pub_key, len) == 0; + } /** * \brief Performs Ed25519 signature verification. diff --git a/src/Mesh.cpp b/src/Mesh.cpp index f12715742d..1bda9e9626 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -52,14 +52,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint32_t auth_code; memcpy(&auth_code, &pkt->payload[i], 4); i += 4; uint8_t flags = pkt->payload[i++]; + uint8_t path_sz = flags & 0x03; // NEW v1.11+: lower 2 bits is path hash size uint8_t len = pkt->payload_len - i; - if (pkt->path_len >= len) { // TRACE has reached end of given path + uint8_t offset = pkt->path_len << path_sz; + if (offset >= len) { // TRACE has reached end of given path onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len); - } else if (self_id.isHashMatch(&pkt->payload[i + pkt->path_len]) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { + } else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { // append SNR (Not hash!) - pkt->path[pkt->path_len] = (int8_t) (pkt->getSNR()*4); - pkt->path_len += PATH_HASH_SIZE; + pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4); uint32_t d = getDirectRetransmitDelay(pkt); return ACTION_RETRANSMIT_DELAYED(5, d); // schedule with priority 5 (for now), maybe make configurable? From 031fa1e704da52c8d31dba1799fbf19b65ed14ba Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Thu, 20 Nov 2025 21:58:42 -0800 Subject: [PATCH 126/409] Changed uint to a uint8_t --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 4 ++-- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/BaseChatMesh.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4136818cee..becbb75912 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -541,7 +541,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->isAdmin()) { // a CLI command uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index de06b4c675..78588f0ff6 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -394,7 +394,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 20d9921b95..ca07f496b1 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -550,7 +550,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from->isAdmin()) { // a CLI command uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (sender_timestamp > from->last_timestamp) { // prevent replay attacks if (flags == TXT_TYPE_PLAIN) { @@ -608,7 +608,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } } -bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) { +bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len) { MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from "); #ifdef MESH_DEBUG mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE); diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 00d9c698a2..1b0d83b31f 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -127,7 +127,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; - virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len); + virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len); void sendAckTo(const ClientInfo& dest, uint32_t ack_hash); private: FILESYSTEM* _fs; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index b4072657b2..4ab3e03bcb 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -166,7 +166,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { uint32_t timestamp; memcpy(×tamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = data[4] >> 2; // message attempt number, and other flags + uint8_t flags = data[4] >> 2; // message attempt number, and other flags // len can be > original length, but 'text' will be padded with zeroes data[len] = 0; // need to make a C string again, with null terminator From 454f6b2583496cc4937b111ab4c7d6eb3b79e9e7 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 17:57:49 +1100 Subject: [PATCH 127/409] rename adverts --- variants/promicro/platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 90962d27c1..78ea5fa1e4 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -42,7 +42,7 @@ build_src_filter = ${Promicro.build_src_filter} + build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Repeater"' + -D ADVERT_NAME='"ProMicro Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -60,7 +60,7 @@ build_src_filter = ${Promicro.build_src_filter} + + build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Room"' + -D ADVERT_NAME='"ProMicro Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -132,7 +132,7 @@ lib_deps = ${Promicro.lib_deps} extends = Promicro build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Sensor"' + -D ADVERT_NAME='"ProMicro Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' From 5a3ea64a97dc0cc419ee553d0da5c4c7656b7858 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 18:15:30 +1100 Subject: [PATCH 128/409] Repeater: add adc.multiplier setting --- examples/simple_repeater/MyMesh.cpp | 4 ++++ src/MeshCore.h | 2 ++ src/helpers/CommonCLI.cpp | 14 ++++++++++++-- src/helpers/CommonCLI.h | 1 + variants/promicro/PromicroBoard.h | 14 +++++++++++++- 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4136818cee..091d790197 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -710,6 +710,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.gps_enabled = 0; _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + + _prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier } void MyMesh::begin(FILESYSTEM *fs) { @@ -733,6 +735,8 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif diff --git a/src/MeshCore.h b/src/MeshCore.h index 94bf351d82..d65c2b93d7 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -42,6 +42,8 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; + virtual void setAdcMultiplier(float multiplier) {}; + virtual float getAdcMultiplier() const { return 1.0f; } virtual const char* getManufacturerName() const = 0; virtual void onBeforeTransmit() { } virtual void onAfterTransmit() { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 93773cceec..b33d71aa35 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -70,7 +70,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 - // 166 + file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 + // 170 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -83,6 +84,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); + _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f); // sanitise bad bridge pref values _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1); @@ -148,7 +150,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 - // 166 + file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 + // 170 file.close(); } @@ -331,6 +334,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "bridge.secret", 13) == 0) { sprintf(reply, "> %s", _prefs->bridge_secret); #endif + } else if (memcmp(config, "adc.multiplier", 14) == 0) { + sprintf(reply, "> %s", StrHelper::ftoa(_prefs->adc_multiplier)); } else { sprintf(reply, "??: %s", config); } @@ -523,6 +528,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch savePrefs(); strcpy(reply, "OK"); #endif + } else if (memcmp(config, "adc.multiplier ", 15) == 0) { + _prefs->adc_multiplier = atof(&config[15]); + _board->setAdcMultiplier(_prefs->adc_multiplier); + savePrefs(); + strcpy(reply, "OK"); } else { sprintf(reply, "unknown config: %s", config); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index a665e014f9..068783ab1a 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -47,6 +47,7 @@ struct NodePrefs { // persisted to file uint32_t gps_interval; // in seconds uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; + float adc_multiplier; }; class CommonCLICallbacks { diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index e4b674153f..9dfb7b2f11 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -23,6 +23,7 @@ class PromicroBoard : public mesh::MainBoard { protected: uint8_t startup_reason; uint8_t btn_prev_state; + float adc_mult = ADC_MULTIPLIER; public: void begin(); @@ -39,7 +40,18 @@ class PromicroBoard : public mesh::MainBoard { raw += analogRead(PIN_VBAT_READ); } raw = raw / BATTERY_SAMPLES; - return (ADC_MULTIPLIER * raw); + return (adc_mult * raw); + } + + void setAdcMultiplier(float multiplier) override { + if (multiplier == 0.0f) { + adc_mult = ADC_MULTIPLIER;} + else { + adc_mult = multiplier; + } + } + float getAdcMultiplier() const override { + return adc_mult; } const char* getManufacturerName() const override { From e13c064487f11a99eaad173b556e0a4dc92a1350 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 21:46:55 +1100 Subject: [PATCH 129/409] add board.setAdcMultiplier to room server and sensor --- examples/simple_room_server/MyMesh.cpp | 2 ++ examples/simple_sensor/SensorMesh.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index de06b4c675..7b575e6faa 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -641,6 +641,8 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 20d9921b95..96a3791d18 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -740,6 +740,8 @@ void SensorMesh::begin(FILESYSTEM* fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif From fc93d84fb8eac695fe79f602ad0e70d59dd07823 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 21 Nov 2025 23:44:17 +1100 Subject: [PATCH 130/409] tweaks get/set adcMultiplier logic --- src/MeshCore.h | 4 ++-- src/helpers/CommonCLI.cpp | 21 +++++++++++++++++---- variants/promicro/PromicroBoard.h | 9 +++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/MeshCore.h b/src/MeshCore.h index d65c2b93d7..11a6a5b41a 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -42,8 +42,8 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; - virtual void setAdcMultiplier(float multiplier) {}; - virtual float getAdcMultiplier() const { return 1.0f; } + virtual bool setAdcMultiplier(float multiplier) { return false; }; + virtual float getAdcMultiplier() const { return 0.0f; } virtual const char* getManufacturerName() const = 0; virtual void onBeforeTransmit() { } virtual void onAfterTransmit() { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b33d71aa35..17b2b75320 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -335,7 +335,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", _prefs->bridge_secret); #endif } else if (memcmp(config, "adc.multiplier", 14) == 0) { - sprintf(reply, "> %s", StrHelper::ftoa(_prefs->adc_multiplier)); + float adc_mult = _board->getAdcMultiplier(); + if (adc_mult == 0.0f) { + strcpy(reply, "Error: unsupported by this board"); + } else { + sprintf(reply, "> %.3f", adc_mult); + } } else { sprintf(reply, "??: %s", config); } @@ -530,9 +535,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch #endif } else if (memcmp(config, "adc.multiplier ", 15) == 0) { _prefs->adc_multiplier = atof(&config[15]); - _board->setAdcMultiplier(_prefs->adc_multiplier); - savePrefs(); - strcpy(reply, "OK"); + if (_board->setAdcMultiplier(_prefs->adc_multiplier)) { + savePrefs(); + if (_prefs->adc_multiplier == 0.0f) { + strcpy(reply, "OK - using default board multiplier"); + } else { + sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier); + } + } else { + _prefs->adc_multiplier = 0.0f; + strcpy(reply, "Error: unsupported by this board"); + }; } else { sprintf(reply, "unknown config: %s", config); } diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 9dfb7b2f11..dc20e5500c 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -43,15 +43,20 @@ class PromicroBoard : public mesh::MainBoard { return (adc_mult * raw); } - void setAdcMultiplier(float multiplier) override { + bool setAdcMultiplier(float multiplier) override { if (multiplier == 0.0f) { adc_mult = ADC_MULTIPLIER;} else { adc_mult = multiplier; } + return true; } float getAdcMultiplier() const override { - return adc_mult; + if (adc_mult == 0.0f) { + return ADC_MULTIPLIER; + } else { + return adc_mult; + } } const char* getManufacturerName() const override { From 07e7e2d44bfd68abbe87f73a853b04d76b37ddf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= Date: Sat, 22 Nov 2025 02:06:44 +0100 Subject: [PATCH 131/409] companion: Suspend radio when hibernating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should significantly reduce power consumption in hibernation. Fixes: #1014 Signed-off-by: Jaroslav Škarvada Signed-off-by: Frieder Schrempf # generalize for all radios and UIs --- examples/companion_radio/ui-new/UITask.cpp | 1 + examples/companion_radio/ui-orig/UITask.cpp | 6 ++++-- src/helpers/radiolib/CustomSX1262Wrapper.h | 3 +++ src/helpers/radiolib/RadioLibWrappers.h | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 086f825999..d8d778c333 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -650,6 +650,7 @@ void UITask::shutdown(bool restart){ _board->reboot(); } else { _display->turnOff(); + radio_driver.powerOff(); _board->powerOff(); } } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d37..89dda11609 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -292,10 +292,12 @@ void UITask::shutdown(bool restart){ #endif // PIN_BUZZER - if (restart) + if (restart) { _board->reboot(); - else + } else { + radio_driver.powerOff(); _board->powerOff(); + } } void UITask::loop() { diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 119f6dced9..1afee5e8bd 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -19,4 +19,7 @@ class CustomSX1262Wrapper : public RadioLibWrapper { int sf = ((CustomSX1262 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + virtual void powerOff() override { + ((CustomSX1262 *)_radio)->sleep(false); + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 25cc53589a..3c26d37272 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -21,6 +21,7 @@ class RadioLibWrapper : public mesh::Radio { RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } void begin() override; + virtual void powerOff() { _radio->sleep(); } int recvRaw(uint8_t* bytes, int sz) override; uint32_t getEstAirtimeFor(int len_bytes) override; bool startSendRaw(const uint8_t* bytes, int len) override; From 0f565323a092fbf13354a1d908e9564694c0f113 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 19 Nov 2025 11:40:00 +0100 Subject: [PATCH 132/409] variants: WisMesh Tag: Enable DC/DC regulator According to the documentation and experiments on other boards using NRF52 + SX1262 this reduces the power consumption significantly. This assumes that the hardware actually has the inductor for the internal DC/DC regulator populated which is very likely. Even if not, it won't hurt. Signed-off-by: Frieder Schrempf --- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 68ce2fd8d4..28f6f713d3 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -21,6 +21,8 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void RAKWismeshTagBoard::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; + pinMode(PIN_VBAT_READ, INPUT); pinMode(PIN_USER_BTN, INPUT_PULLUP); From b9b82fcf1bfa57ce4978b9e3e466343dc79e156f Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 20 Nov 2025 09:09:33 +0100 Subject: [PATCH 133/409] variants: WisMesh Tag: Enable status LED Use the blue LED as status LED. This prevents the blue LED from being always-on and draining the battery. Instead use it as status LED with blink patterns as other companion devices do. Signed-off-by: Frieder Schrempf --- variants/rak_wismesh_tag/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/rak_wismesh_tag/variant.h b/variants/rak_wismesh_tag/variant.h index b0e51efc85..3b8e079f4e 100644 --- a/variants/rak_wismesh_tag/variant.h +++ b/variants/rak_wismesh_tag/variant.h @@ -66,7 +66,7 @@ #define LED_BLUE (36) #define LED_GREEN (35) -//#define PIN_STATUS_LED LED_BLUE +#define PIN_STATUS_LED LED_BLUE #define LED_BUILTIN LED_GREEN #define LED_PIN LED_GREEN #define LED_STATE_ON HIGH From 11f119a7fb7f868d276d495abd7c8755db234e08 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 19 Nov 2025 12:01:07 +0100 Subject: [PATCH 134/409] variants: XIAO NRF52: Enable DC/DC regulator This reduces the power consumption by approximately 25%. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 03bb674ec0..a709b1c38a 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -23,6 +23,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void XiaoNrf52Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); From c76d337a00e6db7ef110e25366bd9a9b8726f5cb Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 19 Nov 2025 17:25:05 +0100 Subject: [PATCH 135/409] variants: XIAO NRF52: Enable user button The Xiao nRF52840 combined with the Wio-SX1262 is often used for cheap and compact DIY companion nodes. The Wio actually has an onboard pushbutton that can be used as user button. Enable support for the button. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 4 ++++ variants/xiao_nrf52/platformio.ini | 7 +++++++ variants/xiao_nrf52/target.cpp | 4 ++++ variants/xiao_nrf52/target.h | 5 +++++ variants/xiao_nrf52/variant.h | 2 +- 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index a709b1c38a..396880abc7 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -29,6 +29,10 @@ void XiaoNrf52Board::begin() { pinMode(VBAT_ENABLE, OUTPUT); digitalWrite(VBAT_ENABLE, HIGH); +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT); +#endif + #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); #endif diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 888e9493ba..edbf6275ed 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -26,10 +26,13 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_WIRE_SCL=D6 -D PIN_WIRE_SDA=D7 + -D PIN_USER_BTN=PIN_BUTTON1 + -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${nrf52_base.build_src_filter} + + +<../variants/xiao_nrf52> + + debug_tool = jlink upload_protocol = nrfutil lib_deps = ${nrf52_base.lib_deps} @@ -41,6 +44,7 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = ${Xiao_nrf52.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 @@ -52,6 +56,7 @@ build_flags = build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -62,6 +67,7 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = ${Xiao_nrf52.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D QSPIFLASH=1 @@ -70,6 +76,7 @@ build_flags = build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index 41142eb662..c9c02d215d 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -2,6 +2,10 @@ #include "target.h" #include +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + XiaoNrf52Board board; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); diff --git a/variants/xiao_nrf52/target.h b/variants/xiao_nrf52/target.h index 86f546b8a0..e1ea2a6b88 100644 --- a/variants/xiao_nrf52/target.h +++ b/variants/xiao_nrf52/target.h @@ -9,6 +9,11 @@ #include #include +#ifdef DISPLAY_CLASS + #include + extern DISPLAY_CLASS display; +#endif + extern XiaoNrf52Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index c54f3c2fa5..c888a8333c 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -38,7 +38,7 @@ extern "C" #define LED_STATE_ON (1) // State when LED is litted // Buttons -#define PIN_BUTTON1 (PINS_COUNT) +#define PIN_BUTTON1 (0) // Digital PINs static const uint8_t D0 = 0 ; From 4a8dcb4906ba4452e6fc0e99898bb5cd67d4cad3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 19 Nov 2025 17:26:15 +0100 Subject: [PATCH 136/409] variants: XIAO NRF52: Support power-off via user button Add the necessary code to properly power-off the Xiao + Wio companions. This way we can achieve around 15 microamps of power consumption in the off state. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/XiaoNrf52Board.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index b229507ab5..f37660123d 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -46,6 +46,24 @@ class XiaoNrf52Board : public mesh::MainBoard { NVIC_SystemReset(); } + void powerOff() override { + // set led on and wait for button release before poweroff + digitalWrite(PIN_LED, LOW); +#ifdef PIN_USER_BTN + while(digitalRead(PIN_USER_BTN) == LOW); +#endif + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_BLUE, HIGH); + digitalWrite(PIN_LED, HIGH); + +#ifdef PIN_USER_BTN + // configure button press to wake up when in powered off state + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); +#endif + + sd_power_system_off(); + } + bool startOTAUpdate(const char* id, char reply[]) override; }; From 048bd268a100bb1aad4dbfaff0a5d51cff242695 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 20 Nov 2025 10:58:14 +0100 Subject: [PATCH 137/409] companion: ui: Respect LED_STATE_ON for status LED The current logic only works for active high LEDs. Some boards need an active low level control and therefore they set LED_STATE_ON to 0. Take this into account and use the correct LED pattern for both cases. Signed-off-by: Frieder Schrempf --- examples/companion_radio/ui-new/UITask.cpp | 2 +- examples/companion_radio/ui-orig/UITask.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index d8d778c333..f12667c704 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -618,7 +618,7 @@ void UITask::userLedHandler() { led_state = 0; next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment; } - digitalWrite(PIN_STATUS_LED, led_state); + digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON); } #endif } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 89dda11609..1fef8572e9 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -269,7 +269,7 @@ void UITask::userLedHandler() { state = 0; next_change = cur_time + LED_CYCLE_MILLIS - last_increment; } - digitalWrite(PIN_STATUS_LED, state); + digitalWrite(PIN_STATUS_LED, state == LED_STATE_ON); } #endif } From 5235516dc7033f69d285eb91a86b49fce3378636 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Thu, 20 Nov 2025 10:59:37 +0100 Subject: [PATCH 138/409] variants: XIAO NRF52: Enable status LED Fix the active state of the LEDs (active low) and enable the status LED. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/variant.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index c888a8333c..3f4d7afeb1 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -34,8 +34,9 @@ extern "C" #define LED_RED (11) #define LED_GREEN (13) #define LED_BLUE (12) +#define PIN_STATUS_LED (LED_BLUE) -#define LED_STATE_ON (1) // State when LED is litted +#define LED_STATE_ON (0) // State when LED is on // Buttons #define PIN_BUTTON1 (0) From 32d622d96964881463c9f7bbdd5b301862078da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= Date: Sat, 22 Nov 2025 02:06:44 +0100 Subject: [PATCH 139/409] variants: Heltec T114: Disable LED and GPS when powering off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should reduce power consumption in hibernation. Signed-off-by: Jaroslav Škarvada --- variants/heltec_t114/T114Board.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 49d1ec3731..0f7fc47fa5 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -48,6 +48,13 @@ class T114Board : public mesh::MainBoard { } void powerOff() override { + #ifdef LED_PIN + digitalWrite(LED_PIN, HIGH); + #endif + #if ENV_INCLUDE_GPS == 1 + pinMode(GPS_EN, OUTPUT); + digitalWrite(GPS_EN, LOW); + #endif sd_power_system_off(); } From 7723a4cb34e5a8b380e378fb2c2170975dc85efe Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Sat, 22 Nov 2025 10:31:47 +0100 Subject: [PATCH 140/409] variants: Heltec T114: Enable DC/DC regulator According to the documentation and experiments on other boards using NRF52 + SX1262 this reduces the power consumption significantly. Signed-off-by: Frieder Schrempf --- variants/heltec_t114/T114Board.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index f8d170b5df..f46a1a84bc 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -21,6 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void T114Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT_READ, INPUT); From d84e61546692be65c5c2f311fe4057b95b445ca1 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 141/409] 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 0000000000..b734fe6b94 --- /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 dc58f0ea83ad12653075fef31b9afe4175d1671d Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 24 Nov 2025 22:56:55 +1100 Subject: [PATCH 142/409] * BUG FIX: repeater remote admin, flood login should invalidate the client->out_path --- examples/simple_repeater/MyMesh.cpp | 8 ++++++-- examples/simple_repeater/MyMesh.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 091d790197..a9be6c40e3 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -82,7 +82,7 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn #endif } -uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { ClientInfo* client = NULL; if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); @@ -123,6 +123,10 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } } + if (is_flood) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; @@ -438,7 +442,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request - reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes // TODO } else { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index d8a20486a5..98bce78759 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -113,7 +113,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); - uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 0e903de72cf6740ab5be070b3fd97dd0e0b3884f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 25 Nov 2025 15:09:51 +1100 Subject: [PATCH 143/409] * BUG FIX: same remote login fix as repeater --- examples/simple_sensor/SensorMesh.cpp | 8 ++++++-- examples/simple_sensor/SensorMesh.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 96a3791d18..6116c4debc 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -326,7 +326,7 @@ int SensorMesh::getAGCResetInterval() const { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } -uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { ClientInfo* client; if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); @@ -359,6 +359,10 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } + if (is_flood) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; @@ -451,7 +455,7 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con data[len] = 0; // ensure null terminator uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request - reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes // TODO } else { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 00d9c698a2..627ef54966 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -148,7 +148,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t pending_sf; uint8_t pending_cr; - uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 30ccc1fa0148c7af036cd05a8eecd444f644b83b Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 25 Nov 2025 15:12:48 +1100 Subject: [PATCH 144/409] * BUG FIX: remote login fix same as repeater --- examples/simple_room_server/MyMesh.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 7b575e6faa..89505a3b07 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -332,6 +332,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } + if (packet->isRouteFlood()) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? From eafbd85d1706698cbc09ca674d49743a9bda4445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 25 Nov 2025 11:53:21 +0000 Subject: [PATCH 145/409] Add RAK4631 support for rs232 bridge --- src/helpers/bridges/RS232Bridge.cpp | 2 ++ variants/rak4631/platformio.ini | 44 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 554e8fcce2..773328555e 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -15,6 +15,8 @@ void RS232Bridge::begin() { #if defined(ESP32) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); +#elif defined(RAK_4631) + ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(NRF52_PLATFORM) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(RP2040_PLATFORM) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 7db67abffc..37800c06cd 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -45,6 +45,50 @@ build_src_filter = ${rak4631.build_src_filter} + +<../examples/simple_repeater> +[env:RAK_4631_repeater_bridge_rs232_tx0_rx0] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=19 + -D WITH_RS232_BRIDGE_TX=20 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +build_src_filter = ${rak4631.build_src_filter} + + + + + +<../examples/simple_repeater> + +[env:RAK_4631_repeater_bridge_rs232_tx1_rx1] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial1 + -D WITH_RS232_BRIDGE_RX=15 + -D WITH_RS232_BRIDGE_TX=16 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +build_src_filter = ${rak4631.build_src_filter} + + + + + +<../examples/simple_repeater> + [env:RAK_4631_room_server] extends = rak4631 build_flags = From baedddb25dead095d439ce458ed7439f10ffde44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 25 Nov 2025 16:19:28 +0000 Subject: [PATCH 146/409] Rename RS232 bridge environments and update build flags for Serial1 and Serial2 --- variants/rak4631/platformio.ini | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 37800c06cd..b335785583 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -45,7 +45,7 @@ build_src_filter = ${rak4631.build_src_filter} + +<../examples/simple_repeater> -[env:RAK_4631_repeater_bridge_rs232_tx0_rx0] +[env:RAK_4631_repeater_bridge_rs232_serial1] extends = rak4631 build_flags = ${rak4631.build_flags} @@ -55,9 +55,10 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 - -D WITH_RS232_BRIDGE=Serial2 - -D WITH_RS232_BRIDGE_RX=19 - -D WITH_RS232_BRIDGE_TX=20 + -D 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 @@ -66,8 +67,8 @@ build_src_filter = ${rak4631.build_src_filter} + + +<../examples/simple_repeater> - -[env:RAK_4631_repeater_bridge_rs232_tx1_rx1] + +[env:RAK_4631_repeater_bridge_rs232_serial2] extends = rak4631 build_flags = ${rak4631.build_flags} @@ -77,9 +78,9 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 - -D WITH_RS232_BRIDGE=Serial1 - -D WITH_RS232_BRIDGE_RX=15 - -D WITH_RS232_BRIDGE_TX=16 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX + -D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 From 5b7d73866cd16d3248fb4598ab0e0185414efdc4 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 25 Nov 2025 19:41:01 +0100 Subject: [PATCH 147/409] fix building issues with heltec wireless paper and heltec tracker --- variants/heltec_tracker/platformio.ini | 8 ++++++++ variants/heltec_tracker/target.h | 2 +- variants/heltec_wireless_paper/platformio.ini | 12 ++++++++++-- variants/heltec_wireless_paper/target.h | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 4f48ac21ab..797eafdca4 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -6,6 +6,14 @@ build_flags = -I variants/heltec_tracker -D HELTEC_LORA_V3 -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial + -D ESP32_CPU_FREQ=80 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 8ac5eb720c..23fab16e14 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include <../heltec_v3/HeltecV3Board.h> #include #include #include diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index c9ad758b13..9cf7615371 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -5,12 +5,20 @@ build_flags = ${esp32_base.build_flags} -I variants/heltec_wireless_paper -D HELTEC_WIRELESS_PAPER + -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D P_LORA_TX_LED=18 - ; -D PIN_BOARD_SDA=17 - ; -D PIN_BOARD_SCL=18 + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=45 -D PIN_VBAT_READ=20 diff --git a/variants/heltec_wireless_paper/target.h b/variants/heltec_wireless_paper/target.h index 65b972d0e3..b89c486fb2 100644 --- a/variants/heltec_wireless_paper/target.h +++ b/variants/heltec_wireless_paper/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include <../heltec_v3/HeltecV3Board.h> #include #include #include From e98c79ae480190e9452aa45f33de1d4a1aa47fba Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 25 Nov 2025 19:45:51 +0100 Subject: [PATCH 148/409] added missing NonBlockingRTTTL dependency, added USB and WIFI companions --- variants/thinknode_m2/platformio.ini | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini index fb691d920b..b2ebca7325 100644 --- a/variants/thinknode_m2/platformio.ini +++ b/variants/thinknode_m2/platformio.ini @@ -145,10 +145,49 @@ build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 +[env:ThinkNode_M2_companion_radio_usb] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${ThinkNode_M2.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M2_companion_radio_wifi] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${ThinkNode_M2.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M2.lib_deps} densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 [env:ThinkNode_M2_companion_radio_serial] extends = ThinkNode_M2 From 6c7b5390e2d8fdcfacebb358d4740b06d9ad4d29 Mon Sep 17 00:00:00 2001 From: zaquaz Date: Wed, 26 Nov 2025 18:37:56 -0800 Subject: [PATCH 149/409] Remove default setting, since it is handled in MyMesh --- examples/companion_radio/DataStore.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 058389fe9a..00e25cb2a6 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -200,11 +200,6 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; - - // Initialize defaults for any missing fields (backward compatibility) - memset(&_prefs, 0, sizeof(_prefs)); - node_lat = 0.0; - node_lon = 0.0; file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 From 3ddfdd477b0402eaa71d1600770d9e50731b1fa7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 27 Nov 2025 21:34:52 +1100 Subject: [PATCH 150/409] Revert "add heltec_v4 tft expansion box" This reverts commit 310618e6899337b37fb3a9268f8e6571bf1f760c. --- src/helpers/ui/SSD1306Display.cpp | 14 +- src/helpers/ui/SSD1306Display.h | 9 +- src/helpers/ui/ST7789LCDDisplay.cpp | 5 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4/HeltecV4Board.cpp | 6 +- variants/heltec_v4/platformio.ini | 265 ++++----------------------- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 6 +- 8 files changed, 44 insertions(+), 267 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 4e7fd10ad0..c9da0cf8d5 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,10 +7,6 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { - if (!_isOn) { - if (_peripher_power) _peripher_power->claim(); - _isOn = true; - } #ifdef DISPLAY_ROTATION display.setRotation(DISPLAY_ROTATION); #endif @@ -19,18 +15,12 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { display.ssd1306_command(SSD1306_DISPLAYON); - if (!_isOn) { - if (_peripher_power) _peripher_power->claim(); - _isOn = true; - } + _isOn = true; } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); - if (_isOn) { - if (_peripher_power) _peripher_power->release(); - _isOn = false; - } + _isOn = false; } void SSD1306Display::clear() { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index d843da85b2..1a3a9602bb 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -5,7 +5,6 @@ #include #define SSD1306_NO_SPLASH #include -#include #ifndef PIN_OLED_RESET #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) @@ -19,16 +18,10 @@ class SSD1306Display : public DisplayDriver { Adafruit_SSD1306 display; bool _isOn; uint8_t _color; - RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), - display(128, 64, &Wire, PIN_OLED_RESET), - _peripher_power(peripher_power) - { - _isOn = false; - } + SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } bool begin(); bool isOn() override { return _isOn; } diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index a686c0c8a6..87f9b8ad64 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -25,13 +25,10 @@ bool ST7789LCDDisplay::begin() { pinMode(PIN_TFT_LEDA_CTL, OUTPUT); digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - pinMode(PIN_TFT_RST, OUTPUT); - digitalWrite(PIN_TFT_RST, LOW); - delay(10); digitalWrite(PIN_TFT_RST, HIGH); // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) + #ifdef LILYGO_TDECK displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); #endif diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index 5b960ca198..a807714855 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include class ST7789LCDDisplay : public DisplayDriver { - #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) + #ifdef LILYGO_TDECK SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ class ST7789LCDDisplay : public DisplayDriver { { _isOn = false; } -#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) +#elif LILYGO_TDECK ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 92f9343767..f143db36f1 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -86,9 +86,5 @@ void HeltecV4Board::begin() { } const char* HeltecV4Board::getManufacturerName() const { - #ifdef HELTEC_LORA_V4_TFT - return "Heltec V4 TFT"; - #else - return "Heltec V4 OLED"; - #endif + return "Heltec V4"; } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ba75900940..c26a5bc69d 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,9 +20,11 @@ build_flags = -D P_LORA_PA_POWER=7 ;power en -D P_LORA_PA_EN=2 -D P_LORA_PA_TX_EN=46 ;enable tx + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=LOW + -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true @@ -45,44 +47,10 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[heltec_v4_oled] -extends = Heltec_lora32_v4 -build_flags = - ${Heltec_lora32_v4.build_flags} - -D HELTEC_LORA_V4_OLED - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 - -D ENV_PIN_SDA=4 - -D ENV_PIN_SCL=3 -build_src_filter= ${Heltec_lora32_v4.build_src_filter} -lib_deps = ${Heltec_lora32_v4.lib_deps} - -[heltec_v4_tft] +[env:heltec_v4_repeater] extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} - -D HELTEC_LORA_V4_TFT - -D PIN_BOARD_SDA=4 - -D PIN_BOARD_SCL=3 - -D DISPLAY_SCALE_X=2.5 - -D DISPLAY_SCALE_Y=3.75 - -D PIN_TFT_RST=18 - -D PIN_TFT_VDD_CTL=-1 - -D PIN_TFT_LEDA_CTL=21 - -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH - -D PIN_TFT_CS=15 - -D PIN_TFT_DC=16 - -D PIN_TFT_SCL=17 - -D PIN_TFT_SDA=33 -build_src_filter= ${Heltec_lora32_v4.build_src_filter} -lib_deps = - ${Heltec_lora32_v4.lib_deps} - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 - -[env:heltec_v4_repeater] -extends = heltec_v4_oled -build_flags = - ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Repeater"' -D ADVERT_LAT=0.0 @@ -91,18 +59,18 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/simple_repeater> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 [env:heltec_v4_repeater_bridge_espnow] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 @@ -113,18 +81,18 @@ build_flags = ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + +<../examples/simple_repeater> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_room_server] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Room"' -D ADVERT_LAT=0.0 @@ -133,50 +101,50 @@ build_flags = -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/simple_room_server> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_terminal_chat] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_usb] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_ble] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -187,20 +155,20 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_wifi] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -210,21 +178,21 @@ build_flags = -D WIFI_PWD='"mypwd"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_sensor] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} - -D ADVERT_NAME='"Heltec v4 Sensor"' + ${Heltec_lora32_v4.build_flags} + -D ADVERT_NAME='"Heltec v3 Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -233,172 +201,9 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/simple_sensor> lib_deps = - ${heltec_v4_oled.lib_deps} - ${esp32_ota.lib_deps} - - -[env:heltec_v4_tft_repeater] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -D ADVERT_NAME='"Heltec Repeater"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=50 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - +<../examples/simple_repeater> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - bakercp/CRC32 @ ^2.0.0 - - -[env:heltec_v4_tft_repeater_bridge_espnow] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -D ADVERT_NAME='"ESPNow Bridge"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=50 - -D WITH_ESPNOW_BRIDGE=1 -; -D BRIDGE_DEBUG=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - + - +<../examples/simple_repeater> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - -[env:heltec_v4_tft_room_server] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -D ADVERT_NAME='"Heltec Room"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D ROOM_PASSWORD='"hello"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - +<../examples/simple_room_server> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - -[env:heltec_v4_tft_terminal_chat] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<../examples/simple_secure_chat/main.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_usb] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=ST7789LCDDisplay -; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 -; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_ble] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=ST7789LCDDisplay - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 ; dynamic, random PIN - -D AUTO_SHUTDOWN_MILLIVOLTS=3400 - -D BLE_DEBUG_LOGGING=1 - -D OFFLINE_QUEUE_SIZE=256 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_wifi] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=ST7789LCDDisplay - -D WIFI_DEBUG_LOGGING=1 - -D WIFI_SSID='"myssid"' - -D WIFI_PWD='"mypwd"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - + - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_sensor] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D ADVERT_NAME='"Heltec v4 Sensor"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=3 - -D ENV_PIN_SCL=4 - -D DISPLAY_CLASS=ST7789LCDDisplay -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - + - +<../examples/simple_sensor> -lib_deps = - ${heltec_v4_tft.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 0d2bd4976c..015c3a8e76 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display(&(board.periph_power)); + DISPLAY_CLASS display; MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index 00d2adab6d..a153b2af35 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -9,11 +9,7 @@ #include #include #ifdef DISPLAY_CLASS -#ifdef HELTEC_LORA_V4_OLED - #include -#elif defined(HELTEC_LORA_V4_TFT) - #include -#endif + #include #include #endif From d0f6def4f9b4d03ea226a9b2246aa594081a5049 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 27 Nov 2025 21:49:04 +0100 Subject: [PATCH 151/409] thinknode_m5: initial port --- src/helpers/ui/GxEPDDisplay.cpp | 8 + variants/thinknode_m5/ThinknodeM5Board.cpp | 40 ++++ variants/thinknode_m5/ThinknodeM5Board.h | 18 ++ variants/thinknode_m5/pins_arduino.h | 28 +++ variants/thinknode_m5/platformio.ini | 217 +++++++++++++++++++++ variants/thinknode_m5/target.cpp | 57 ++++++ variants/thinknode_m5/target.h | 32 +++ variants/thinknode_m5/variant.h | 21 ++ 8 files changed, 421 insertions(+) create mode 100644 variants/thinknode_m5/ThinknodeM5Board.cpp create mode 100644 variants/thinknode_m5/ThinknodeM5Board.h create mode 100644 variants/thinknode_m5/pins_arduino.h create mode 100644 variants/thinknode_m5/platformio.ini create mode 100644 variants/thinknode_m5/target.cpp create mode 100644 variants/thinknode_m5/target.h create mode 100644 variants/thinknode_m5/variant.h diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 34e31e3038..a8a9b209dd 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -5,9 +5,17 @@ #define DISPLAY_ROTATION 3 #endif +#ifdef ESP32 + SPIClass SPI1 = SPIClass(FSPI); +#endif + bool GxEPDDisplay::begin() { display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); +#ifdef ESP32 + SPI1.begin(PIN_DISPLAY_SCLK, PIN_DISPLAY_MISO, PIN_DISPLAY_MOSI, PIN_DISPLAY_CS); +#else SPI1.begin(); +#endif display.init(115200, true, 2, false); display.setRotation(DISPLAY_ROTATION); setTextSize(1); // Default to size 1 diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp new file mode 100644 index 0000000000..647440196a --- /dev/null +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -0,0 +1,40 @@ +#include "ThinknodeM5Board.h" + + + +void ThinknodeM5Board::begin() { + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle + delay(20); // allow power rail to discharge + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on + delay(120); // give display time to bias on cold boot + ESP32Board::begin(); + pinMode(PIN_STATUS_LED, OUTPUT); // init power led + } + + void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_deep_sleep_start(); + } + + void ThinknodeM5Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t ThinknodeM5Board::getBattMilliVolts() { + analogReadResolution(12); + analogSetPinAttenuation(PIN_VBAT_READ, ADC_11db); + + uint32_t mv = 0; + for (int i = 0; i < 8; ++i) { + mv += analogReadMilliVolts(PIN_VBAT_READ); + delayMicroseconds(200); + } + mv /= 8; + + analogReadResolution(10); + return static_cast(mv * ADC_MULTIPLIER ); +} + + const char* ThinknodeM5Board::getManufacturerName() const { + return "Elecrow ThinkNode M2"; + } diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h new file mode 100644 index 0000000000..58a3ae300d --- /dev/null +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include + +class ThinknodeM5Board : public ESP32Board { + +public: + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; \ No newline at end of file diff --git a/variants/thinknode_m5/pins_arduino.h b/variants/thinknode_m5/pins_arduino.h new file mode 100644 index 0000000000..a5dee3635d --- /dev/null +++ b/variants/thinknode_m5/pins_arduino.h @@ -0,0 +1,28 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial +static const uint8_t TX = GPS_TX; +static const uint8_t RX = GPS_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = P_LORA_NSS; +static const uint8_t SCK = P_LORA_SCLK; +static const uint8_t MOSI = P_LORA_MISO; +static const uint8_t MISO = P_LORA_MOSI; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = PIN_BOARD_SCL; +static const uint8_t SDA = PIN_BOARD_SDA; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini new file mode 100644 index 0000000000..54ad8bd59b --- /dev/null +++ b/variants/thinknode_m5/platformio.ini @@ -0,0 +1,217 @@ +[ThinkNode_M5] +extends = esp32_base +board = ESP32-S3-WROOM-1-N4 +build_flags = ${esp32_base.build_flags} + -I variants/thinknode_m5 + -D THINKNODE_M5 + -D GPS_RX=19 + -D GPS_TX=20 + -D PIN_VEXT_EN=46 + -D PIN_BUZZER=9 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_BOARD_SCL=1 + -D PIN_BOARD_SDA=2 + -D P_LORA_DIO_1=4 + -D P_LORA_NSS=17 + -D P_LORA_RESET=6 ; RADIOLIB_NC + -D P_LORA_BUSY=5 ; DIO2 = 38 + -D P_LORA_SCLK=16 + -D P_LORA_MISO=7 + -D P_LORA_MOSI=15 + -D PIN_USER_BTN=21 + -D PIN_STATUS_LED=1 + -D LED_STATE_ON=HIGH + -D PIN_LED=3 + -D DISPLAY_ROTATION=4 + -D DISPLAY_CLASS=GxEPDDisplay + -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 + -D EINK_SCALE_X=1.5625f + -D EINK_SCALE_Y=1.5625f + -D EINK_X_OFFSET=0 + -D EINK_Y_OFFSET=10 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 + -D MESH_DEBUG=1 +build_src_filter = ${esp32_base.build_src_filter} + + + + + + + +<../variants/thinknode_m5> +lib_deps = ${esp32_base.lib_deps} + zinggjm/GxEPD2 @ 1.6.2 + bakercp/CRC32 @ ^2.0.0 + +[env:ThinkNode_M5_Repeater] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"Thinknode M2 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +; [env:ThinkNode_M5_Repeater_bridge_rs232] +; extends = ThinkNode_M5 +; build_src_filter = ${ThinkNode_M5.build_src_filter} +; + +; +<../examples/simple_repeater/*.cpp> +; build_flags = +; ${ThinkNode_M5.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${ThinkNode_M5.lib_deps} +; ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_Repeater_bridge_espnow] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_room_server] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"Thinknode M2 Room Server"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_terminal_chat] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M5_companion_radio_ble] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_usb] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_wifi] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"Livebox-633C"' + -D WIFI_PWD='"vvQUHGSxsWd7fKMYSr"' +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_serial] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D SERIAL_TX=D6 + -D SERIAL_RX=D7 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp new file mode 100644 index 0000000000..c65696c20b --- /dev/null +++ b/variants/thinknode_m5/target.cpp @@ -0,0 +1,57 @@ +#include +#include "target.h" + +ThinknodeM5Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); +// pinMode(21, INPUT); +// pinMode(48, OUTPUT); + #if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h new file mode 100644 index 0000000000..75b68ae49e --- /dev/null +++ b/variants/thinknode_m5/target.h @@ -0,0 +1,32 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +//#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ThinknodeM5Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + + \ No newline at end of file diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h new file mode 100644 index 0000000000..6118bd75d0 --- /dev/null +++ b/variants/thinknode_m5/variant.h @@ -0,0 +1,21 @@ +#define I2C_SCL 1 +#define I2C_SDA 2 +#define PIN_VBAT_READ 8 +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (2.11F) +#define PIN_BUZZER 9 +#define PIN_VEXT_EN_ACTIVE HIGH +#define PIN_VEXT_EN 46 +#define PIN_USER_BTN 21 +#define PIN_LED 3 +#define PIN_STATUS_LED 1 +#define PIN_PWRBTN 14 + +#define PIN_DISPLAY_MISO (-1) +#define PIN_DISPLAY_MOSI (45) +#define PIN_DISPLAY_SCLK (38) +#define PIN_DISPLAY_CS (39) +#define PIN_DISPLAY_DC (40) +#define PIN_DISPLAY_RST (41) +#define PIN_DISPLAY_BUSY (42) +#define DISP_BACKLIGHT (5) \ No newline at end of file From 24edd3cf209b9a8362d103b535d2f18c7958ae56 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 27 Nov 2025 22:55:21 +0100 Subject: [PATCH 152/409] thinknode_m5: add pca9557 expander --- variants/thinknode_m5/ThinknodeM5Board.cpp | 3 +-- variants/thinknode_m5/platformio.ini | 7 ++++--- variants/thinknode_m5/target.cpp | 6 ++++++ variants/thinknode_m5/target.h | 3 +++ variants/thinknode_m5/variant.h | 6 +++--- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 647440196a..2aabb0e412 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -1,7 +1,6 @@ #include "ThinknodeM5Board.h" - void ThinknodeM5Board::begin() { pinMode(PIN_VEXT_EN, OUTPUT); digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle @@ -9,7 +8,7 @@ void ThinknodeM5Board::begin() { digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on delay(120); // give display time to bias on cold boot ESP32Board::begin(); - pinMode(PIN_STATUS_LED, OUTPUT); // init power led + // pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 54ad8bd59b..a78b1ba242 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -19,9 +19,9 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 - -D PIN_STATUS_LED=1 - -D LED_STATE_ON=HIGH - -D PIN_LED=3 +# -D PIN_STATUS_LED=1 ; leds are on PCA !!! +# -D LED_STATE_ON=HIGH +# -D PIN_LED=3 -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 @@ -45,6 +45,7 @@ build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 + maxpromer/PCA9557-arduino [env:ThinkNode_M5_Repeater] extends = ThinkNode_M5 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index c65696c20b..2c388b588a 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -15,6 +15,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +PCA9557 expander (0x18, &Wire1); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -26,6 +27,11 @@ bool radio_init() { rtc_clock.begin(Wire); // pinMode(21, INPUT); // pinMode(48, OUTPUT); + Wire1.begin(48, 47); + expander.pinMode(4, OUTPUT); // eink + expander.pinMode(5, OUTPUT); // peripherals + expander.digitalWrite(4, HIGH); + expander.digitalWrite(5, HIGH); #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); return radio.std_init(&spi); diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 75b68ae49e..7fa749f808 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -12,11 +12,14 @@ #include #include #endif +#include +#include extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +extern PCA9557 expander; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 6118bd75d0..d312fcbfc8 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -7,8 +7,8 @@ #define PIN_VEXT_EN_ACTIVE HIGH #define PIN_VEXT_EN 46 #define PIN_USER_BTN 21 -#define PIN_LED 3 -#define PIN_STATUS_LED 1 +//#define PIN_LED 3 +//#define PIN_STATUS_LED 1 #define PIN_PWRBTN 14 #define PIN_DISPLAY_MISO (-1) @@ -18,4 +18,4 @@ #define PIN_DISPLAY_DC (40) #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) -#define DISP_BACKLIGHT (5) \ No newline at end of file +//#define DISP_BACKLIGHT (5) \ No newline at end of file From dfec6d3483432159c34d734ad1d31b60f3e6b28b Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 09:57:58 +0100 Subject: [PATCH 153/409] thinknode_m5: tx_led --- variants/thinknode_m5/ThinknodeM5Board.cpp | 16 ++++++++++------ variants/thinknode_m5/ThinknodeM5Board.h | 9 +++++++++ variants/thinknode_m5/platformio.ini | 4 ++-- variants/thinknode_m5/target.cpp | 8 ++------ variants/thinknode_m5/target.h | 2 -- variants/thinknode_m5/variant.h | 3 ++- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 2aabb0e412..f178caadf2 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -1,14 +1,18 @@ #include "ThinknodeM5Board.h" +PCA9557 expander (0x18, &Wire1); void ThinknodeM5Board::begin() { - pinMode(PIN_VEXT_EN, OUTPUT); - digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle - delay(20); // allow power rail to discharge - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on - delay(120); // give display time to bias on cold boot + // Start expander + Wire1.begin(48, 47); + expander.pinMode(EXP_PIN_POWER, OUTPUT); // eink + expander.pinMode(EXP_PIN_BACKLIGHT, OUTPUT); // peripherals + expander.pinMode(EXP_PIN_LED, OUTPUT); // peripherals + expander.digitalWrite(EXP_PIN_POWER, HIGH); + expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); + expander.digitalWrite(EXP_PIN_LED, LOW); + ESP32Board::begin(); - // pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h index 58a3ae300d..3c120027b7 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.h +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -4,6 +4,9 @@ #include #include #include +#include + +extern PCA9557 expander; class ThinknodeM5Board : public ESP32Board { @@ -15,4 +18,10 @@ class ThinknodeM5Board : public ESP32Board { uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; + void onBeforeTransmit() override { + expander.digitalWrite(EXP_PIN_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + expander.digitalWrite(EXP_PIN_LED, LOW); // turn TX LED off + } }; \ No newline at end of file diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index a78b1ba242..11f563775e 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -6,11 +6,10 @@ build_flags = ${esp32_base.build_flags} -D THINKNODE_M5 -D GPS_RX=19 -D GPS_TX=20 - -D PIN_VEXT_EN=46 -D PIN_BUZZER=9 - -D PIN_VEXT_EN_ACTIVE=HIGH -D PIN_BOARD_SCL=1 -D PIN_BOARD_SDA=2 + -D P_LORA_EN=46 -D P_LORA_DIO_1=4 -D P_LORA_NSS=17 -D P_LORA_RESET=6 ; RADIOLIB_NC @@ -19,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 + -D EXP_PIN_LED=1 # -D PIN_STATUS_LED=1 ; leds are on PCA !!! # -D LED_STATE_ON=HIGH # -D PIN_LED=3 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index 2c388b588a..fdc5ca7a7c 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -15,7 +15,6 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; -PCA9557 expander (0x18, &Wire1); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -27,11 +26,8 @@ bool radio_init() { rtc_clock.begin(Wire); // pinMode(21, INPUT); // pinMode(48, OUTPUT); - Wire1.begin(48, 47); - expander.pinMode(4, OUTPUT); // eink - expander.pinMode(5, OUTPUT); // peripherals - expander.digitalWrite(4, HIGH); - expander.digitalWrite(5, HIGH); + pinMode(P_LORA_EN, OUTPUT); + digitalWrite(P_LORA_EN, HIGH); #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); return radio.std_init(&spi); diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 7fa749f808..c3584a7052 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -12,8 +12,6 @@ #include #include #endif -#include -#include extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index d312fcbfc8..7ee5f5ccd9 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -18,4 +18,5 @@ #define PIN_DISPLAY_DC (40) #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) -//#define DISP_BACKLIGHT (5) \ No newline at end of file +#define EXP_PIN_BACKLIGHT (5) +#define EXP_PIN_POWER (4) \ No newline at end of file From ee4e87c3ee545821a9b017e73525a3f943d9f2ba Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 10:33:19 +0100 Subject: [PATCH 154/409] thinknode_m5: manage baclight --- examples/companion_radio/ui-new/UITask.cpp | 6 +++++- src/helpers/ui/GxEPDDisplay.cpp | 9 +++++++++ variants/thinknode_m5/ThinknodeM5Board.cpp | 3 +-- variants/thinknode_m5/platformio.ini | 3 +++ variants/thinknode_m5/target.cpp | 2 -- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 16751d2021..fe26277f7b 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -716,10 +716,14 @@ void UITask::loop() { _analogue_pin_read_millis = millis(); } #endif -#if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN) +#if defined(BACKLIGHT_BTN) if (millis() > next_backlight_btn_check) { bool touch_state = digitalRead(PIN_BUTTON2); +#if defined(DISP_BACKLIGHT) digitalWrite(DISP_BACKLIGHT, !touch_state); +#elif defined(EXP_PIN_BACKLIGHT) + expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state); +#endif next_backlight_btn_check = millis() + 300; } #endif diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index a8a9b209dd..ad47754bf9 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -1,6 +1,11 @@ #include "GxEPDDisplay.h" +#ifdef EXP_PIN_BACKLIGHT + #include + extern PCA9557 expander; +#endif + #ifndef DISPLAY_ROTATION #define DISPLAY_ROTATION 3 #endif @@ -35,6 +40,8 @@ void GxEPDDisplay::turnOn() { if (!_init) begin(); #if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN) digitalWrite(DISP_BACKLIGHT, HIGH); +#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN) + expander.digitalWrite(EXP_PIN_BACKLIGHT, HIGH); #endif _isOn = true; } @@ -42,6 +49,8 @@ void GxEPDDisplay::turnOn() { void GxEPDDisplay::turnOff() { #if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN) digitalWrite(DISP_BACKLIGHT, LOW); +#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN) + expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); #endif _isOn = false; } diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index f178caadf2..916f448334 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -3,7 +3,7 @@ PCA9557 expander (0x18, &Wire1); void ThinknodeM5Board::begin() { - // Start expander + // Start expander and configure pins Wire1.begin(48, 47); expander.pinMode(EXP_PIN_POWER, OUTPUT); // eink expander.pinMode(EXP_PIN_BACKLIGHT, OUTPUT); // peripherals @@ -11,7 +11,6 @@ void ThinknodeM5Board::begin() { expander.digitalWrite(EXP_PIN_POWER, HIGH); expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); expander.digitalWrite(EXP_PIN_LED, LOW); - ESP32Board::begin(); } diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 11f563775e..353a9c52a9 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -18,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 + -D PIN_BUTTON2=14 -D EXP_PIN_LED=1 # -D PIN_STATUS_LED=1 ; leds are on PCA !!! # -D LED_STATE_ON=HIGH @@ -29,6 +30,8 @@ build_flags = ${esp32_base.build_flags} -D EINK_SCALE_Y=1.5625f -D EINK_X_OFFSET=0 -D EINK_Y_OFFSET=10 + -D BACKLIGHT_BTN=PIN_BUTTON2 + -D AUTO_OFF_MILLIS=0 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=3.3 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index fdc5ca7a7c..fa55961066 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -24,8 +24,6 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); -// pinMode(21, INPUT); -// pinMode(48, OUTPUT); pinMode(P_LORA_EN, OUTPUT); digitalWrite(P_LORA_EN, HIGH); #if defined(P_LORA_SCLK) From 1c0017b634a68a12e232c46b2c7b4d4d47759858 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 11:11:13 +0100 Subject: [PATCH 155/409] thinknode_m5: gps support --- examples/companion_radio/ui-new/UITask.cpp | 15 +++++++++++++-- .../sensors/EnvironmentSensorManager.cpp | 4 ++++ variants/thinknode_m5/ThinknodeM5Board.cpp | 5 +++++ variants/thinknode_m5/pins_arduino.h | 4 ++-- variants/thinknode_m5/platformio.ini | 19 +++++++++++-------- variants/thinknode_m5/target.cpp | 9 ++++++++- variants/thinknode_m5/target.h | 4 +++- variants/thinknode_m5/variant.h | 8 +++++++- 8 files changed, 53 insertions(+), 15 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index fe26277f7b..59a1b2dee0 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -260,13 +260,24 @@ class HomeScreen : public UIScreen { #if ENV_INCLUDE_GPS == 1 } else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); + char buf[50]; int y = 18; - display.drawTextLeftAlign(0, y, _task->getGPSState() ? "gps on" : "gps off"); + bool gps_state = _task->getGPSState(); +#ifdef PIN_GPS_SWITCH + bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); + if (gps_state != hw_gps_state) { + strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); + } else { + strcpy(buf, gps_state ? "gps on" : "gps off"); + } +#else + strcpy(buf, gps_state ? "gps on" : "gps off"); +#endif + display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { y = y + 12; display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { - char buf[50]; strcpy(buf, nmea->isValid()?"fix":"no fix"); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 79dc87e5db..bb675c2762 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -548,7 +548,11 @@ void EnvironmentSensorManager::initBasicGPS() { delay(1000); // We'll consider GPS detected if we see any data on Serial1 +#ifdef ENV_SKIP_GPS_DETECT + gps_detected = true; +#else gps_detected = (Serial1.available() > 0); +#endif if (gps_detected) { MESH_DEBUG_PRINTLN("GPS detected"); diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 916f448334..5adc8c0059 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -11,6 +11,11 @@ void ThinknodeM5Board::begin() { expander.digitalWrite(EXP_PIN_POWER, HIGH); expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); expander.digitalWrite(EXP_PIN_LED, LOW); + +#ifdef PIN_GPS_SWITCH + pinMode(PIN_GPS_SWITCH, INPUT); +#endif + ESP32Board::begin(); } diff --git a/variants/thinknode_m5/pins_arduino.h b/variants/thinknode_m5/pins_arduino.h index a5dee3635d..408ed23609 100644 --- a/variants/thinknode_m5/pins_arduino.h +++ b/variants/thinknode_m5/pins_arduino.h @@ -12,8 +12,8 @@ #define USB_PID 0x1001 // Serial -static const uint8_t TX = GPS_TX; -static const uint8_t RX = GPS_RX; +static const uint8_t TX = PIN_GPS_TX; +static const uint8_t RX = PIN_GPS_RX; // Default SPI will be mapped to Radio static const uint8_t SS = P_LORA_NSS; diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 353a9c52a9..23db506a61 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,9 +3,8 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 + -I src/helpres/sensors -D THINKNODE_M5 - -D GPS_RX=19 - -D GPS_TX=20 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 -D PIN_BOARD_SDA=2 @@ -19,10 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 -D PIN_BUTTON2=14 - -D EXP_PIN_LED=1 -# -D PIN_STATUS_LED=1 ; leds are on PCA !!! -# -D LED_STATE_ON=HIGH -# -D PIN_LED=3 + -D EXP_PIN_LED=1 ; led is on bus expander -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 @@ -32,6 +28,7 @@ build_flags = ${esp32_base.build_flags} -D EINK_Y_OFFSET=10 -D BACKLIGHT_BTN=PIN_BUTTON2 -D AUTO_OFF_MILLIS=0 + -D DISABLE_DIAGNOSTIC_OUTPUT -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=3.3 -D SX126X_CURRENT_LIMIT=140 @@ -40,7 +37,11 @@ build_flags = ${esp32_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_RX_BOOSTED_GAIN=1 -D MESH_DEBUG=1 + -D ENV_INCLUDE_GPS=1 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 build_src_filter = ${esp32_base.build_src_filter} + + + + + @@ -49,6 +50,7 @@ lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 maxpromer/PCA9557-arduino + stevemarple/MicroNMEA @ ^2.0.6 [env:ThinkNode_M5_Repeater] extends = ThinkNode_M5 @@ -109,7 +111,7 @@ lib_deps = ${esp32_ota.lib_deps} [env:ThinkNode_M5_room_server] -extends = ThinkNode_M5 +extends = ThinkNonde_M5 build_src_filter = ${ThinkNode_M5.build_src_filter} +<../examples/simple_room_server> build_flags = @@ -148,9 +150,10 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 +; -D GPS_NMEA_DEBUG build_src_filter = ${ThinkNode_M5.build_src_filter} + + diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index fa55961066..8208d2c43f 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -1,5 +1,6 @@ #include #include "target.h" +#include ThinknodeM5Board board; @@ -14,7 +15,13 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index c3584a7052..2af42095cf 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #ifdef DISPLAY_CLASS #include #include @@ -16,7 +18,7 @@ extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; extern PCA9557 expander; #ifdef DISPLAY_CLASS diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 7ee5f5ccd9..9b82416b0d 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -19,4 +19,10 @@ #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) #define EXP_PIN_BACKLIGHT (5) -#define EXP_PIN_POWER (4) \ No newline at end of file +#define EXP_PIN_POWER (4) + +#define PIN_GPS_EN (11) +#define PIN_GPS_RESET (13) +#define PIN_GPS_RX (20) +#define PIN_GPS_TX (19) +#define PIN_GPS_SWITCH (10) \ No newline at end of file From c641beabd32f564f9064a520efd90afd1df524fa Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 16:37:10 +0800 Subject: [PATCH 156/409] https://github.com/meshcore-dev/MeshCore/issues/989 - persist GPS enabled state to preferences Add GPS configuration to NodePrefs structure and persist the GPS enabled state when toggled via UI. This ensures GPS settings are retained across device restarts. --- examples/companion_radio/DataStore.cpp | 4 ++++ examples/companion_radio/MyMesh.cpp | 7 +++++++ examples/companion_radio/NodePrefs.h | 2 ++ examples/companion_radio/ui-new/UITask.cpp | 3 +++ 4 files changed, 16 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 00e25cb2a6..7f5761f3cb 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -222,6 +222,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read(pad, 2); // 78 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 + file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 + file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.close(); } @@ -254,6 +256,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write(pad, 2); // 78 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 + file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 + file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 09d866c9e2..a94975b3e0 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -739,6 +739,8 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; + _prefs.gps_enabled = 0; // GPS disabled by default + _prefs.gps_interval = 0; // No automatic GPS updates by default //_prefs.rx_delay_base = 10.0f; enable once new algo fixed } @@ -776,6 +778,7 @@ void MyMesh::begin(bool has_display) { _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); + _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -803,6 +806,10 @@ void MyMesh::begin(bool has_display) { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); + +#if ENV_INCLUDE_GPS == 1 + sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); +#endif } const char *MyMesh::getNodeName() { diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 13c9f88423..e9db5444fe 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -25,4 +25,6 @@ struct NodePrefs { // persisted to file uint32_t ble_pin; uint8_t advert_loc_policy; uint8_t buzzer_quiet; + uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) + uint32_t gps_interval; // GPS read interval in seconds }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 59a1b2dee0..4cc47e2386 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -863,13 +863,16 @@ void UITask::toggleGPS() { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { _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(); _next_refresh = 0; break; } From 88fb173297d667894df4b21faffab84ddea14d7a Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 17:20:02 +0800 Subject: [PATCH 157/409] add configurable GPS update interval Make GPS update interval configurable via settings instead of using hardcoded 1 second value. The interval is persisted from preferences and can be adjusted at runtime through the sensor manager settings interface --- examples/companion_radio/MyMesh.cpp | 3 +++ src/helpers/sensors/EnvironmentSensorManager.cpp | 11 ++++++++++- src/helpers/sensors/EnvironmentSensorManager.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a94975b3e0..be39b7fa6a 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -809,6 +809,9 @@ void MyMesh::begin(bool has_display) { #if ENV_INCLUDE_GPS == 1 sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval_str); #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index bb675c2762..53c381f7ce 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -521,6 +521,15 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val } return true; } + if (strcmp(name, "gps_interval") == 0) { + uint32_t interval_seconds = atoi(value); + if (interval_seconds > 0) { + gps_update_interval_ms = interval_seconds * 1000; + } else { + gps_update_interval_ms = 1000; // Default to 1 second if 0 + } + return true; + } #endif return false; // not supported } @@ -708,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + 1000; + next_gps_update = millis() + gps_update_interval_ms; } #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 5f1c08e2e7..6dd532e6bd 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -25,6 +25,7 @@ class EnvironmentSensorManager : public SensorManager { bool gps_detected = false; bool gps_active = false; + uint32_t gps_update_interval_ms = 1000; // Default 1 second #if ENV_INCLUDE_GPS LocationProvider* _location; From 678915ef3b6ad3e9bb398bfa797b5c6f832f73f5 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 17:29:59 +0800 Subject: [PATCH 158/409] add GPS interval validation and bounds checking --- examples/companion_radio/MyMesh.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index be39b7fa6a..91bb7358d2 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -779,6 +779,7 @@ void MyMesh::begin(bool has_display) { _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 + _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -809,9 +810,11 @@ void MyMesh::begin(bool has_display) { #if ENV_INCLUDE_GPS == 1 sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); - char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) - sprintf(interval_str, "%u", _prefs.gps_interval); - sensors.setSettingValue("gps_interval", interval_str); + if (_prefs.gps_interval > 0) { + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval_str); + } #endif } From 4aebc57add0c61a37e31bcf18b8a7c5620539785 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 18:02:08 +0800 Subject: [PATCH 159/409] fixed gps init value --- examples/companion_radio/MyMesh.cpp | 9 --------- examples/companion_radio/main.cpp | 10 ++++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 91bb7358d2..035e06a74d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -807,15 +807,6 @@ void MyMesh::begin(bool has_display) { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); - -#if ENV_INCLUDE_GPS == 1 - sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); - if (_prefs.gps_interval > 0) { - char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) - sprintf(interval_str, "%u", _prefs.gps_interval); - sensors.setSettingValue("gps_interval", interval_str); - } -#endif } const char *MyMesh::getNodeName() { diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d93..15b461f3ba 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -216,6 +216,16 @@ void setup() { sensors.begin(); +#if ENV_INCLUDE_GPS == 1 + // Apply GPS preferences after sensors.begin() so gps_detected is set + sensors.setSettingValue("gps", the_mesh.getNodePrefs()->gps_enabled ? "1" : "0"); + if (the_mesh.getNodePrefs()->gps_interval > 0) { + char interval_str[12]; + sprintf(interval_str, "%u", the_mesh.getNodePrefs()->gps_interval); + sensors.setSettingValue("gps_interval", interval_str); + } +#endif + #ifdef DISPLAY_CLASS ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved #endif From 39503ad0b48515e527d38ff2bf477b207b5bd1b5 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 18:35:10 +0800 Subject: [PATCH 160/409] move GPS preference initialization to UITask --- examples/companion_radio/main.cpp | 10 ---------- examples/companion_radio/ui-new/UITask.cpp | 13 +++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 15b461f3ba..82c8c21d93 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -216,16 +216,6 @@ void setup() { sensors.begin(); -#if ENV_INCLUDE_GPS == 1 - // Apply GPS preferences after sensors.begin() so gps_detected is set - sensors.setSettingValue("gps", the_mesh.getNodePrefs()->gps_enabled ? "1" : "0"); - if (the_mesh.getNodePrefs()->gps_interval > 0) { - char interval_str[12]; - sprintf(interval_str, "%u", the_mesh.getNodePrefs()->gps_interval); - sensors.setSettingValue("gps_interval", interval_str); - } -#endif - #ifdef DISPLAY_CLASS ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved #endif diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 4cc47e2386..ebdeb6d3b3 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -537,6 +537,19 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #endif _node_prefs = node_prefs; + +#if ENV_INCLUDE_GPS == 1 + // Apply GPS preferences from stored prefs + if (_sensors != NULL && _node_prefs != NULL) { + _sensors->setSettingValue("gps", _node_prefs->gps_enabled ? "1" : "0"); + if (_node_prefs->gps_interval > 0) { + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _node_prefs->gps_interval); + _sensors->setSettingValue("gps_interval", interval_str); + } + } +#endif + if (_display != NULL) { _display->turnOn(); } From 62e180dc0fa1b5af9e55308bc91d60ec1fc98ba8 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 19:02:00 +0800 Subject: [PATCH 161/409] changed ms to sec --- src/helpers/sensors/EnvironmentSensorManager.cpp | 6 +++--- src/helpers/sensors/EnvironmentSensorManager.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 53c381f7ce..2362eda7ee 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -524,9 +524,9 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val if (strcmp(name, "gps_interval") == 0) { uint32_t interval_seconds = atoi(value); if (interval_seconds > 0) { - gps_update_interval_ms = interval_seconds * 1000; + gps_update_interval_sec = interval_seconds; } else { - gps_update_interval_ms = 1000; // Default to 1 second if 0 + gps_update_interval_sec = 1; // Default to 1 second if 0 } return true; } @@ -717,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + gps_update_interval_ms; + next_gps_update = millis() + gps_update_interval_sec; } #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 6dd532e6bd..f176a33f5f 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -25,7 +25,7 @@ class EnvironmentSensorManager : public SensorManager { bool gps_detected = false; bool gps_active = false; - uint32_t gps_update_interval_ms = 1000; // Default 1 second + uint32_t gps_update_interval_sec = 1; // Default 1 second #if ENV_INCLUDE_GPS LocationProvider* _location; From df3cb3d192ab655b467ecb22e4fb41c3041c1673 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 20:29:52 +0800 Subject: [PATCH 162/409] _location->loop() should be in the next tick --- src/helpers/sensors/EnvironmentSensorManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2362eda7ee..b072bcb0d5 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -695,9 +695,9 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS - _location->loop(); - if (millis() > next_gps_update) { + _location->loop(); + if(gps_active){ #ifdef RAK_WISBLOCK_GPS if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) { @@ -717,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + gps_update_interval_sec; + next_gps_update = millis() + (gps_update_interval_sec * 1000); } #endif } From cfb7ed876ca7a424d7d1007ebb271aa08765e77b Mon Sep 17 00:00:00 2001 From: csrutil Date: Sun, 30 Nov 2025 09:45:56 +0800 Subject: [PATCH 163/409] CMD_SET_CUSTOM_VAR will update gps and gps_interval --- examples/companion_radio/MyMesh.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 035e06a74d..3aed2da700 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1525,6 +1525,17 @@ void MyMesh::handleCmdFrame(size_t len) { *np++ = 0; // modify 'cmd_frame', replace ':' with null bool success = sensors.setSettingValue(sp, np); if (success) { + #if ENV_INCLUDE_GPS == 1 + // Update node preferences for GPS settings + if (strcmp(sp, "gps") == 0) { + _prefs.gps_enabled = (np[0] == '1') ? 1 : 0; + savePrefs(); + } else if (strcmp(sp, "gps_interval") == 0) { + uint32_t interval_seconds = atoi(np); + _prefs.gps_interval = constrain(interval_seconds, 0, 86400); + savePrefs(); + } + #endif writeOKFrame(); } else { writeErrFrame(ERR_CODE_ILLEGAL_ARG); From e054597a189cd7749c5327351d27e9dc92d0d125 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 30 Nov 2025 18:32:10 +1100 Subject: [PATCH 164/409] * ver 1.11.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 9c22532d62..1fcc5697df 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "13 Nov 2025" +#define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.10.0" +#define FIRMWARE_VERSION "v1.11.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 98bce78759..ed9f0c5fce 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -68,11 +68,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 8641caaf97..e7f1fee83d 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 9259ad9c1a..c320eb447c 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #define FIRMWARE_ROLE "sensor" From 052f17738cd05e133265f3370f039497311bfe90 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Sun, 30 Nov 2025 10:52:33 +0100 Subject: [PATCH 165/409] add default LED_STATE_ON for boards that don't have it defined --- examples/companion_radio/ui-new/UITask.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 32d5f3a0dc..02c3cafbd1 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -8,6 +8,10 @@ #include #include +#ifndef LED_STATE_ON + #define LED_STATE_ON 1 +#endif + #ifdef PIN_BUZZER #include #endif @@ -50,7 +54,7 @@ class UITask : public AbstractUITask { UIScreen* curr; void userLedHandler(); - + // Button action handlers char checkDisplayOn(char c); char handleLongPress(char c); From 405f703bfebf5218355d0c07fd9f948ed4c40ef2 Mon Sep 17 00:00:00 2001 From: Florent Date: Mon, 1 Dec 2025 09:40:02 +0100 Subject: [PATCH 166/409] thinknode_m5: fix repeater build --- variants/thinknode_m5/platformio.ini | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 23db506a61..fb2ba3ac28 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,7 +3,7 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 - -I src/helpres/sensors + -I src/helpers/sensors -D THINKNODE_M5 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 @@ -44,7 +44,6 @@ build_src_filter = ${esp32_base.build_src_filter} + + + - + +<../variants/thinknode_m5> lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 @@ -159,6 +158,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -177,6 +177,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -198,6 +199,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -219,6 +221,8 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 From 07d6484b616ef86f340b5faf94bd43fcb415735a Mon Sep 17 00:00:00 2001 From: Florian Lippert <973586+flol@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:29:03 +0100 Subject: [PATCH 167/409] Support for RAK11310 WisBlock --- variants/rak11310/RAK11310Board.cpp | 30 +++++++ variants/rak11310/RAK11310Board.h | 49 +++++++++++ variants/rak11310/platformio.ini | 132 ++++++++++++++++++++++++++++ variants/rak11310/target.cpp | 39 ++++++++ variants/rak11310/target.h | 20 +++++ 5 files changed, 270 insertions(+) create mode 100644 variants/rak11310/RAK11310Board.cpp create mode 100644 variants/rak11310/RAK11310Board.h create mode 100644 variants/rak11310/platformio.ini create mode 100644 variants/rak11310/target.cpp create mode 100644 variants/rak11310/target.h diff --git a/variants/rak11310/RAK11310Board.cpp b/variants/rak11310/RAK11310Board.cpp new file mode 100644 index 0000000000..f45d8148f7 --- /dev/null +++ b/variants/rak11310/RAK11310Board.cpp @@ -0,0 +1,30 @@ +#include "RAK11310Board.h" + +#include +#include + +void RAK11310Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); +#endif + +#ifdef PIN_VBAT_READ + pinMode(PIN_VBAT_READ, INPUT); +#endif + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setSDA(PIN_BOARD_SDA); + Wire.setSCL(PIN_BOARD_SCL); +#endif + + Wire.begin(); + + delay(10); // give sx1262 some time to power up +} + +bool RAK11310Board::startOTAUpdate(const char *id, char reply[]) { + return false; +} diff --git a/variants/rak11310/RAK11310Board.h b/variants/rak11310/RAK11310Board.h new file mode 100644 index 0000000000..ea0f15e26c --- /dev/null +++ b/variants/rak11310/RAK11310Board.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +// from https://github.com/RAKWireless/RAK11300-AT-Command-Firmware/blob/9c48409a43620a828d653501d536473200aa33af/RAK11300-AT-Arduino/batt.cpp#L17-L19 +#define VBAT_MV_PER_LSB (0.806F) // 3.0V ADC range and 12 - bit ADC resolution = 3300mV / 4096 +#define VBAT_DIVIDER (0.6F) // 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) +#define VBAT_DIVIDER_COMP (1.846F) // // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ 26 +#define BATTERY_SAMPLES 8 +#define ADC_MULTIPLIER (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class RAK11310Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#ifdef P_LORA_TX_LED + void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); } + void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); } +#endif + + uint16_t getBattMilliVolts() override { +#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER) + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + return (ADC_MULTIPLIER * raw); +#else + return 0; +#endif + } + + const char *getManufacturerName() const override { return "RAK 11310"; } + + void reboot() override { rp2040.reboot(); } + + bool startOTAUpdate(const char *id, char reply[]) override; +}; diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini new file mode 100644 index 0000000000..df99ea8433 --- /dev/null +++ b/variants/rak11310/platformio.ini @@ -0,0 +1,132 @@ +; RAK11310 +; Pinout from https://github.com/beegee-tokyo/SX126x-Arduino/blob/6be1f87b84ad4d445a38ec53d65be4425f2383f3/src/boards/mcu/board.cpp#L259 + +[rak11310] +extends = rp2040_base +board = rakwireless_rak11300 +board_build.filesystem_size = 0.5m +build_flags = ${rp2040_base.build_flags} + -I variants/rak11310 + -D ARDUINO_RAKWIRELESS_RAK11300=1 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=29 + -D P_LORA_NSS=13 ; CS + -D P_LORA_RESET=14 + -D P_LORA_BUSY=15 + -D P_LORA_SCLK=10 + -D P_LORA_MISO=12 + -D P_LORA_MOSI=11 + -D P_LORA_TX_LED=24 ; green led = 23, blue led = 24 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 +; Debug options + ; -D DEBUG_RP2040_WIRE=1 + ; -D DEBUG_RP2040_SPI=1 + ; -D DEBUG_RP2040_CORE=1 + ; -D RADIOLIB_DEBUG_SPI=1 + ; -D DEBUG_RP2040_PORT=Serial +build_src_filter = ${rp2040_base.build_src_filter} + + + +<../variants/rak11310> +lib_deps = ${rp2040_base.lib_deps} + +[env:rak11310_repeater] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D ADVERT_NAME='"RAK11310 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 = ${rak11310.build_src_filter} + +<../examples/simple_repeater> + +[env:rak11310_repeater_bridge_rs232] +extends = rak11310 +build_flags = ${rak11310.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 WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=8 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + + + +<../examples/simple_repeater> + +[env:rak11310_room_server] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D ADVERT_NAME='"RAK11310 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_room_server> + +[env:rak11310_companion_radio_usb] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = ${rak11310.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; [env:rak11310_companion_radio_ble] +; extends = rak11310 +; build_flags = ${rak11310.build_flags} +; -D MAX_CONTACTS=100 +; -D MAX_GROUP_CHANNELS=8 +; -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${rak11310.build_src_filter} +; +<../examples/companion_radio/*.cpp> +; lib_deps = ${rak11310.lib_deps} +; densaugeo/base64 @ ~1.4.0 + +; [env:rak11310_companion_radio_wifi] +; extends = rak11310 +; build_flags = ${rak11310.build_flags} +; -D MAX_CONTACTS=100 +; -D MAX_GROUP_CHANNELS=8 +; -D WIFI_DEBUG_LOGGING=1 +; -D WIFI_SSID='"myssid"' +; -D WIFI_PWD='"mypwd"' +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${rak11310.build_src_filter} +; +<../examples/companion_radio/*.cpp> +; lib_deps = ${rak11310.lib_deps} +; densaugeo/base64 @ ~1.4.0 + +[env:rak11310_terminal_chat] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${rak11310.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/rak11310/target.cpp b/variants/rak11310/target.cpp new file mode 100644 index 0000000000..dba5bff2c4 --- /dev/null +++ b/variants/rak11310/target.cpp @@ -0,0 +1,39 @@ +#include +#include "target.h" +#include + +RAK11310Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI1); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI1); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/rak11310/target.h b/variants/rak11310/target.h new file mode 100644 index 0000000000..fe45c3f2c7 --- /dev/null +++ b/variants/rak11310/target.h @@ -0,0 +1,20 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +extern RAK11310Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager 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(); From f56172738d3c24b82f27b2ae88dc4c8b58d64027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 2 Dec 2025 10:30:45 +0000 Subject: [PATCH 168/409] Bridge: Fix RAK4631 serial2 GPS conflict --- variants/rak4631/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index b335785583..7293b4d49c 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -81,6 +81,7 @@ build_flags = -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX -D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX + -UENV_INCLUDE_GPS ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 From 69a9a0bce9db9896b155d5c13f0c2d2a3875bc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 2 Dec 2025 10:31:24 +0000 Subject: [PATCH 169/409] Bridge: Add t114 rs232 targets --- src/helpers/bridges/RS232Bridge.cpp | 8 +++--- src/helpers/bridges/RS232Bridge.h | 2 +- variants/heltec_t114/platformio.ini | 38 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 773328555e..0024f6f2f2 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -15,10 +15,9 @@ void RS232Bridge::begin() { #if defined(ESP32) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); -#elif defined(RAK_4631) - ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(NRF52_PLATFORM) - ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); + // Tested with RAK_4631 and T114 + ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(RP2040_PLATFORM) ((SerialUART *)_serial)->setRX(WITH_RS232_BRIDGE_RX); ((SerialUART *)_serial)->setTX(WITH_RS232_BRIDGE_TX); @@ -123,8 +122,7 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) { // Check if packet fits within our maximum payload size if (len > (MAX_TRANS_UNIT + 1)) { - BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, - MAX_TRANS_UNIT + 1); + BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, MAX_TRANS_UNIT + 1); return; } diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 839c0ba09d..8fc1c22c93 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -40,7 +40,7 @@ * Platform Support: * Different platforms require different pin configuration methods: * - ESP32: Uses HardwareSerial::setPins(rx, tx) - * - NRF52: Uses HardwareSerial::setPins(rx, tx) + * - NRF52: Uses Uart::setPins(rx, tx) * - RP2040: Uses SerialUART::setRX(rx) and SerialUART::setTX(tx) * - STM32: Uses HardwareSerial::setRx(rx) and HardwareSerial::setTx(tx) */ diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 91ca78cd46..7b6f5ceef4 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -59,6 +59,25 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +[env:Heltec_t114_without_display_repeater_bridge_rs232] +extends = Heltec_t114 +build_flags = + ${Heltec_t114.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 WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=10 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_t114.build_src_filter} + + + +<../examples/simple_repeater> + [env:Heltec_t114_without_display_room_server] extends = Heltec_t114 build_src_filter = ${Heltec_t114.build_src_filter} @@ -151,6 +170,25 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +[env:Heltec_t114_repeater_bridge_rs232] +extends = Heltec_t114 +build_flags = + ${Heltec_t114.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 WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=10 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_t114_with_display.build_src_filter} + + + +<../examples/simple_repeater> + [env:Heltec_t114_room_server] extends = Heltec_t114_with_display build_src_filter = ${Heltec_t114_with_display.build_src_filter} From dde9b7cc76ece24c753768c63fcdfa5bdbbd2cb3 Mon Sep 17 00:00:00 2001 From: taco Date: Wed, 3 Dec 2025 14:59:57 +1100 Subject: [PATCH 170/409] remove calls to sd_power_mode_set(NRF_POWER_MODE_LOWPWR); this is the default mode, there is no need to call it unless previously changing it. --- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp | 2 -- variants/wio_wm1110/WioWM1110Board.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp index c41a6bc0fe..561ed5049d 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp @@ -11,8 +11,6 @@ void MinewsemiME25LS01Board::begin() { pinMode(PIN_VBAT_READ, INPUT); - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - #ifdef BUTTON_PIN pinMode(BUTTON_PIN, INPUT); pinMode(LED_PIN, OUTPUT); diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index ca3638b344..98e0d616bd 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -23,7 +23,6 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void WioWM1110Board::begin() { startup_reason = BD_STARTUP_NORMAL; - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); NRF_POWER->DCDCEN = 1; pinMode(BATTERY_PIN, INPUT); From e1d3da942be1a4ec98ec37c1a1741084bcf1819c Mon Sep 17 00:00:00 2001 From: taco Date: Wed, 3 Dec 2025 15:58:36 +1100 Subject: [PATCH 171/409] fix DC/DC enable for boards which currently have it. this fixes how the reg1 dc/dc converter is enabled on WisMesh Tag / T1000e / WM1110 and Xiao NRF52 --- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 10 +++++++++- variants/t1000-e/T1000eBoard.cpp | 10 +++++++--- variants/wio_wm1110/WioWM1110Board.cpp | 9 ++++++++- variants/xiao_nrf52/XiaoNrf52Board.cpp | 10 +++++++++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 28f6f713d3..68638f0dde 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -21,7 +21,15 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void RAKWismeshTagBoard::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + + // 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); pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index 4bcdf98aaa..a41abd92ed 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -9,10 +9,14 @@ void T1000eBoard::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; + 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); diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index 98e0d616bd..153d476c42 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -23,7 +23,14 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void WioWM1110Board::begin() { startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + // 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(BATTERY_PIN, INPUT); pinMode(LED_GREEN, OUTPUT); diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 396880abc7..6921892639 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -23,7 +23,15 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void XiaoNrf52Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + + // 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, INPUT); pinMode(VBAT_ENABLE, OUTPUT); From ec375fa24847c25965c5e0c3ac0a87eaf3bcb752 Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Tue, 2 Dec 2025 17:41:31 -0700 Subject: [PATCH 172/409] variants: lilygo_techo: variant: Turn off leds on poweroff Signed-off-by: Luis Garcia --- variants/lilygo_techo/TechoBoard.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 4792153a99..080387976f 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -32,13 +32,13 @@ class TechoBoard : public mesh::MainBoard { void powerOff() override { #ifdef LED_RED - digitalWrite(LED_RED, LOW); + digitalWrite(LED_RED, HIGH); #endif #ifdef LED_GREEN - digitalWrite(LED_GREEN, LOW); + digitalWrite(LED_GREEN, HIGH); #endif #ifdef LED_BLUE - digitalWrite(LED_BLUE, LOW); + digitalWrite(LED_BLUE, HIGH); #endif #ifdef DISP_BACKLIGHT digitalWrite(DISP_BACKLIGHT, LOW); From 1a3f7a7ea985289ed7d2e430e131b1ea4f6739f9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:47:41 +0100 Subject: [PATCH 173/409] Fix BLE semaphore leak in Bluefruit library Patches Bluefruit library to fix semaphore leak bug that causes device lockup when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout). Co-authored-by: Liam Cottle Co-authored-by: oltaco --- arch/nrf52/extra_scripts/patch_bluefruit.py | 198 ++++++++++++++++++++ platformio.ini | 4 +- 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 arch/nrf52/extra_scripts/patch_bluefruit.py diff --git a/arch/nrf52/extra_scripts/patch_bluefruit.py b/arch/nrf52/extra_scripts/patch_bluefruit.py new file mode 100644 index 0000000000..b43bffb5db --- /dev/null +++ b/arch/nrf52/extra_scripts/patch_bluefruit.py @@ -0,0 +1,198 @@ +""" +Bluefruit BLE Patch Script + +Patches Bluefruit library to fix semaphore leak bug that causes device lockup +when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout). + +Patches applied: +1. BLEConnection.h: Add _hvn_qsize member to track semaphore queue size +2. BLEConnection.cpp: Store hvn_qsize and restore semaphore on disconnect + +Bug description: +- When a BLE central disconnects unexpectedly (reason=8 supervision timeout), + the BLE_GATTS_EVT_HVN_TX_COMPLETE event may never fire +- This leaves the _hvn_sem counting semaphore in a decremented state +- Since BLEConnection objects are reused (destructor never called), the + semaphore count is never restored +- Eventually all semaphore counts are exhausted and notify() blocks/fails + +""" + +from pathlib import Path + +Import("env") # pylint: disable=undefined-variable + + +def _patch_ble_connection_header(source: Path) -> bool: + """ + Add _hvn_qsize member variable to BLEConnection class. + + This is needed to restore the semaphore to its correct count on disconnect. + + Returns True if patch was applied or already applied, False on error. + """ + try: + content = source.read_text() + + # Check if already patched + if "_hvn_qsize" in content: + return True # Already patched + + # Find the location to insert - after _phy declaration + original_pattern = ''' uint8_t _phy; + + uint8_t _role;''' + + patched_pattern = ''' uint8_t _phy; + uint8_t _hvn_qsize; + + uint8_t _role;''' + + if original_pattern not in content: + print("Bluefruit patch: WARNING - BLEConnection.h pattern not found") + return False + + content = content.replace(original_pattern, patched_pattern) + source.write_text(content) + + # Verify + if "_hvn_qsize" not in source.read_text(): + return False + + return True + except Exception as e: + print(f"Bluefruit patch: ERROR patching BLEConnection.h: {e}") + return False + + +def _patch_ble_connection_source(source: Path) -> bool: + """ + Patch BLEConnection.cpp to: + 1. Store hvn_qsize in constructor + 2. Restore _hvn_sem semaphore to full count on disconnect + + Returns True if patch was applied or already applied, False on error. + """ + try: + content = source.read_text() + + # Check if already patched (look for the restore loop) + if "uxSemaphoreGetCount(_hvn_sem)" in content: + return True # Already patched + + # Patch 1: Store queue size in constructor + constructor_original = ''' _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);''' + + constructor_patched = ''' _hvn_qsize = hvn_qsize; + _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);''' + + if constructor_original not in content: + print("Bluefruit patch: WARNING - BLEConnection.cpp constructor pattern not found") + return False + + content = content.replace(constructor_original, constructor_patched) + + # Patch 2: Restore semaphore on disconnect + disconnect_original = ''' case BLE_GAP_EVT_DISCONNECTED: + // mark as disconnected + _connected = false; + break;''' + + disconnect_patched = ''' case BLE_GAP_EVT_DISCONNECTED: + // Restore notification semaphore to full count + // This fixes lockup when disconnect occurs with notifications in flight + while (uxSemaphoreGetCount(_hvn_sem) < _hvn_qsize) { + xSemaphoreGive(_hvn_sem); + } + // Release indication semaphore if waiting + if (_hvc_sem) { + _hvc_received = false; + xSemaphoreGive(_hvc_sem); + } + // mark as disconnected + _connected = false; + break;''' + + if disconnect_original not in content: + print("Bluefruit patch: WARNING - BLEConnection.cpp disconnect pattern not found") + return False + + content = content.replace(disconnect_original, disconnect_patched) + source.write_text(content) + + # Verify + verify_content = source.read_text() + if "uxSemaphoreGetCount(_hvn_sem)" not in verify_content: + return False + if "_hvn_qsize = hvn_qsize" not in verify_content: + return False + + return True + except Exception as e: + print(f"Bluefruit patch: ERROR patching BLEConnection.cpp: {e}") + return False + + +def _apply_bluefruit_patches(target, source, env): # pylint: disable=unused-argument + framework_path = env.get("PLATFORMFW_DIR") + if not framework_path: + framework_path = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52") + + if not framework_path: + print("Bluefruit patch: ERROR - framework directory not found") + env.Exit(1) + return + + framework_dir = Path(framework_path) + bluefruit_lib = framework_dir / "libraries" / "Bluefruit52Lib" / "src" + patch_failed = False + + # Patch BLEConnection.h + conn_header = bluefruit_lib / "BLEConnection.h" + if conn_header.exists(): + before = conn_header.read_text() + success = _patch_ble_connection_header(conn_header) + after = conn_header.read_text() + + if success: + if before != after: + print("Bluefruit patch: OK - Applied BLEConnection.h fix (added _hvn_qsize member)") + else: + print("Bluefruit patch: OK - BLEConnection.h already patched") + else: + print("Bluefruit patch: FAILED - BLEConnection.h") + patch_failed = True + else: + print(f"Bluefruit patch: ERROR - BLEConnection.h not found at {conn_header}") + patch_failed = True + + # Patch BLEConnection.cpp + conn_source = bluefruit_lib / "BLEConnection.cpp" + if conn_source.exists(): + before = conn_source.read_text() + success = _patch_ble_connection_source(conn_source) + after = conn_source.read_text() + + if success: + if before != after: + print("Bluefruit patch: OK - Applied BLEConnection.cpp fix (restore semaphore on disconnect)") + else: + print("Bluefruit patch: OK - BLEConnection.cpp already patched") + else: + print("Bluefruit patch: FAILED - BLEConnection.cpp") + patch_failed = True + else: + print(f"Bluefruit patch: ERROR - BLEConnection.cpp not found at {conn_source}") + patch_failed = True + + if patch_failed: + print("Bluefruit patch: CRITICAL - Patch failed! Build aborted.") + env.Exit(1) + + +# Register the patch to run before build +bluefruit_action = env.VerboseAction(_apply_bluefruit_patches, "Applying Bluefruit BLE patches...") +env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", bluefruit_action) + +# Also run immediately to patch before any compilation +_apply_bluefruit_patches(None, None, env) diff --git a/platformio.ini b/platformio.ini index 3907cf64b2..75d37e869b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,7 +79,9 @@ extends = arduino_base platform = nordicnrf52 platform_packages = framework-arduinoadafruitnrf52 @ 1.10700.0 -extra_scripts = create-uf2.py +extra_scripts = + create-uf2.py + arch/nrf52/extra_scripts/patch_bluefruit.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM -D LFS_NO_ASSERT=1 From 6db57677f96e9f2ca0e7f43ada723adbfd030b3c Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Thu, 4 Dec 2025 12:01:00 +0100 Subject: [PATCH 174/409] tracker_l1: enable dc/dc converter --- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index c5c9db650d..34d9a87455 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -23,6 +23,15 @@ void WioTrackerL1Board::begin() { startup_reason = BD_STARTUP_NORMAL; 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); From 73ab0d881361c215b01e34cae9d3ba8064bef576 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:48:01 +0100 Subject: [PATCH 175/409] Improve SerialBLEInterface --- src/helpers/nrf52/SerialBLEInterface.cpp | 368 ++++++++++++++++------- src/helpers/nrf52/SerialBLEInterface.h | 32 +- 2 files changed, 285 insertions(+), 115 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index dbe6f39304..b4811a2076 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -1,193 +1,353 @@ #include "SerialBLEInterface.h" +#include +#include +#include "ble_gap.h" +#include "ble_hci.h" -static SerialBLEInterface* instance; +#define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds + +static SerialBLEInterface* instance = nullptr; void SerialBLEInterface::onConnect(uint16_t connection_handle) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: connected"); - // we now set _isDeviceConnected=true in onSecured callback instead + BLE_DEBUG_PRINTLN("SerialBLEInterface: connected handle=0x%04X", connection_handle); + if (instance) { + instance->_conn_handle = connection_handle; + instance->_isDeviceConnected = false; + instance->clearBuffers(); + } } void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected reason=%d", reason); - if(instance){ - instance->_isDeviceConnected = false; - instance->startAdv(); + BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected handle=0x%04X reason=%u", connection_handle, reason); + if (instance) { + if (instance->_conn_handle == connection_handle) { + instance->_conn_handle = BLE_CONN_HANDLE_INVALID; + instance->_isDeviceConnected = false; + instance->clearBuffers(); + } } } void SerialBLEInterface::onSecured(uint16_t connection_handle) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured"); - if(instance){ - instance->_isDeviceConnected = true; - // no need to stop advertising on connect, as the ble stack does this automatically + BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured handle=0x%04X", connection_handle); + if (instance) { + if (instance->isValidConnection(connection_handle, true)) { + instance->_isDeviceConnected = true; + + // Connection interval units: 1.25ms, supervision timeout units: 10ms + // 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 + + 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"); + } else { + BLE_DEBUG_PRINTLN("Failed to request connection parameter update: %lu", err_code); + } + } else { + BLE_DEBUG_PRINTLN("onSecured: ignoring stale/duplicate callback"); + } } } -void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { +bool SerialBLEInterface::onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request) { + (void)connection_handle; + (void)passkey; + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing passkey request match=%d", match_request); + return true; +} + +void SerialBLEInterface::onPairingComplete(uint16_t connection_handle, uint8_t auth_status) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing complete handle=0x%04X status=%u", connection_handle, auth_status); + if (instance) { + if (instance->isValidConnection(connection_handle)) { + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing successful"); + } else { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing failed, disconnecting"); + instance->disconnect(); + } + } else { + BLE_DEBUG_PRINTLN("onPairingComplete: ignoring stale callback"); + } + } +} + +void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) { + if (!instance) return; + + if (evt->header.evt_id == BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST) { + uint16_t conn_handle = evt->evt.gap_evt.conn_handle; + if (instance->isValidConnection(conn_handle)) { + BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: handle=0x%04X, min_interval=%u, max_interval=%u, latency=%u, timeout=%u", + conn_handle, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.min_conn_interval, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.max_conn_interval, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.slave_latency, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.conn_sup_timeout); + + uint32_t err_code = sd_ble_gap_conn_param_update(conn_handle, NULL); + if (err_code == NRF_SUCCESS) { + BLE_DEBUG_PRINTLN("Accepted CONN_PARAM_UPDATE_REQUEST (using PPCP)"); + } else { + BLE_DEBUG_PRINTLN("ERROR: Failed to accept CONN_PARAM_UPDATE_REQUEST: 0x%08X", err_code); + } + } else { + BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: ignoring stale callback for handle=0x%04X", conn_handle); + } + } +} +void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { instance = this; char charpin[20]; - sprintf(charpin, "%d", pin_code); - + snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code); + + // If we want to control BLE LED ourselves, uncomment this: + // Bluefruit.autoConnLed(false); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(250, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); // increase MTU - Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.begin(); + + // 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 + + 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"); + } else { + BLE_DEBUG_PRINTLN("Failed to set PPCP: %lu", err_code); + } + + Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.setName(device_name); Bluefruit.Security.setMITM(true); Bluefruit.Security.setPIN(charpin); + Bluefruit.Security.setIOCaps(true, false, false); + Bluefruit.Security.setPairPasskeyCallback(onPairingPasskey); + Bluefruit.Security.setPairCompleteCallback(onPairingComplete); Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); Bluefruit.Security.setSecuredCallback(onSecured); - // To be consistent OTA DFU should be added first if it exists - //bledfu.begin(); + Bluefruit.setEventCallback(onBLEEvent); - // Configure and start the BLE Uart service bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bleuart.begin(); - -} - -void SerialBLEInterface::startAdv() { - - BLE_DEBUG_PRINTLN("SerialBLEInterface: starting advertising"); - - // clean restart if already advertising - if(Bluefruit.Advertising.isRunning()){ - BLE_DEBUG_PRINTLN("SerialBLEInterface: already advertising, stopping to allow clean restart"); - Bluefruit.Advertising.stop(); - } + bleuart.setRxCallback(onBleUartRX); - Bluefruit.Advertising.clearData(); // clear advertising data - Bluefruit.ScanResponse.clearData(); // clear scan response data - - // Advertising packet Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower(); - - // Include the BLE UART (AKA 'NUS') 128-bit UUID Bluefruit.Advertising.addService(bleuart); - // Secondary Scan Response packet (optional) - // Since there is no room for 'Name' in Advertising packet Bluefruit.ScanResponse.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(false); // don't restart automatically as we handle it in onDisconnect Bluefruit.Advertising.setInterval(32, 244); - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + Bluefruit.Advertising.setFastTimeout(30); + + Bluefruit.Advertising.restartOnDisconnect(true); } -void SerialBLEInterface::stopAdv() { +void SerialBLEInterface::clearBuffers() { + send_queue_len = 0; + recv_queue_len = 0; + bleuart.flush(); +} - BLE_DEBUG_PRINTLN("SerialBLEInterface: stopping advertising"); - - // we only want to stop advertising if it's running, otherwise an invalid state error is logged by ble stack - if(!Bluefruit.Advertising.isRunning()){ - return; +void SerialBLEInterface::shiftSendQueueLeft() { + if (send_queue_len > 0) { + send_queue_len--; + for (uint8_t i = 0; i < send_queue_len; i++) { + send_queue[i] = send_queue[i + 1]; + } } +} - // stop advertising - Bluefruit.Advertising.stop(); +void SerialBLEInterface::shiftRecvQueueLeft() { + if (recv_queue_len > 0) { + recv_queue_len--; + for (uint8_t i = 0; i < recv_queue_len; i++) { + recv_queue[i] = recv_queue[i + 1]; + } + } +} +bool SerialBLEInterface::isValidConnection(uint16_t handle, bool requireWaitingForSecurity) const { + if (_conn_handle != handle) { + return false; + } + BLEConnection* conn = Bluefruit.Connection(handle); + if (conn == nullptr || !conn->connected()) { + return false; + } + if (requireWaitingForSecurity && _isDeviceConnected) { + return false; + } + return true; } -// ---------- public methods +bool SerialBLEInterface::isAdvertising() const { + ble_gap_addr_t adv_addr; + uint32_t err_code = sd_ble_gap_adv_addr_get(0, &adv_addr); + return (err_code == NRF_SUCCESS); +} -void SerialBLEInterface::enable() { +void SerialBLEInterface::enable() { if (_isEnabled) return; _isEnabled = true; clearBuffers(); + _last_health_check = millis(); - // Start advertising - startAdv(); + Bluefruit.Advertising.start(0); +} + +void SerialBLEInterface::disconnect() { + if (_conn_handle != BLE_CONN_HANDLE_INVALID) { + sd_ble_gap_disconnect(_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); + } } void SerialBLEInterface::disable() { _isEnabled = false; - BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); - -#ifdef RAK_BOARD - Bluefruit.disconnect(Bluefruit.connHandle()); -#else - uint16_t conn_id; - if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) { - Bluefruit.disconnect(conn_id); - } -#endif + BLE_DEBUG_PRINTLN("SerialBLEInterface: disable"); - Bluefruit.Advertising.restartOnDisconnect(false); + disconnect(); Bluefruit.Advertising.stop(); - Bluefruit.Advertising.clearData(); - - stopAdv(); + _last_health_check = 0; } size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { if (len > MAX_FRAME_SIZE) { - BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d", len); + BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%u", (unsigned)len); return 0; } - if (_isDeviceConnected && len > 0) { + bool connected = isConnected(); + if (connected && len > 0) { if (send_queue_len >= FRAME_QUEUE_SIZE) { BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!"); return 0; } - send_queue[send_queue_len].len = len; // add to send queue + send_queue[send_queue_len].len = len; memcpy(send_queue[send_queue_len].buf, src, len); send_queue_len++; - + return len; } return 0; } -#define BLE_WRITE_MIN_INTERVAL 60 - -bool SerialBLEInterface::isWriteBusy() const { - return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write? -} - size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { - if (send_queue_len > 0 // first, check send queue - && millis() >= _last_write + BLE_WRITE_MIN_INTERVAL // space the writes apart - ) { - _last_write = millis(); - bleuart.write(send_queue[0].buf, send_queue[0].len); - BLE_DEBUG_PRINTLN("writeBytes: sz=%d, hdr=%d", (uint32_t)send_queue[0].len, (uint32_t) send_queue[0].buf[0]); - - send_queue_len--; - for (int i = 0; i < send_queue_len; i++) { // delete top item from queue - send_queue[i] = send_queue[i + 1]; + if (send_queue_len > 0) { + if (!isConnected()) { + 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"); + shiftSendQueueLeft(); + } else { + BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry"); + } + } } - } else { - int len = bleuart.available(); - if (len > 0) { - bleuart.readBytes(dest, len); - BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]); - return len; + } + + if (recv_queue_len > 0) { + size_t len = recv_queue[0].len; + memcpy(dest, recv_queue[0].buf, len); + + BLE_DEBUG_PRINTLN("readBytes: sz=%u, hdr=%u", (unsigned)len, (unsigned)dest[0]); + + shiftRecvQueueLeft(); + return len; + } + + // Advertising watchdog: periodically check if advertising is running, restart if not + // Only run when truly disconnected (no connection handle), not during connection establishment + unsigned long now = millis(); + if (_isEnabled && !isConnected() && _conn_handle == BLE_CONN_HANDLE_INVALID) { + if (now - _last_health_check >= BLE_HEALTH_CHECK_INTERVAL) { + _last_health_check = now; + + if (!isAdvertising()) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: advertising watchdog - advertising stopped, restarting"); + Bluefruit.Advertising.start(0); + } } } + return 0; } +void SerialBLEInterface::onBleUartRX(uint16_t conn_handle) { + if (!instance) { + return; + } + + if (instance->_conn_handle != conn_handle || !instance->isConnected()) { + while (instance->bleuart.available() > 0) { + instance->bleuart.read(); + } + return; + } + + while (instance->bleuart.available() > 0) { + if (instance->recv_queue_len >= FRAME_QUEUE_SIZE) { + while (instance->bleuart.available() > 0) { + instance->bleuart.read(); + } + BLE_DEBUG_PRINTLN("onBleUartRX: recv queue full, dropping data"); + break; + } + + int avail = instance->bleuart.available(); + + if (avail > MAX_FRAME_SIZE) { + BLE_DEBUG_PRINTLN("onBleUartRX: WARN: BLE RX overflow, avail=%d, draining all", avail); + uint8_t drain_buf[32]; + while (instance->bleuart.available() > 0) { + int chunk = instance->bleuart.available() > 32 ? 32 : instance->bleuart.available(); + instance->bleuart.readBytes(drain_buf, chunk); + } + continue; + } + + int read_len = avail; + instance->recv_queue[instance->recv_queue_len].len = read_len; + instance->bleuart.readBytes(instance->recv_queue[instance->recv_queue_len].buf, read_len); + instance->recv_queue_len++; + } +} + bool SerialBLEInterface::isConnected() const { - return _isDeviceConnected; + return _isDeviceConnected && Bluefruit.connected() > 0; +} + +bool SerialBLEInterface::isWriteBusy() const { + return send_queue_len >= (FRAME_QUEUE_SIZE - 1); } diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index bf29892d9c..557b86c5e1 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -11,41 +11,51 @@ class SerialBLEInterface : public BaseSerialInterface { BLEUart bleuart; bool _isEnabled; bool _isDeviceConnected; - unsigned long _last_write; + uint16_t _conn_handle; + unsigned long _last_health_check; struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; }; - #define FRAME_QUEUE_SIZE 4 - int send_queue_len; + #define FRAME_QUEUE_SIZE 12 + + uint8_t send_queue_len; Frame send_queue[FRAME_QUEUE_SIZE]; + + uint8_t recv_queue_len; + Frame recv_queue[FRAME_QUEUE_SIZE]; - void clearBuffers() { send_queue_len = 0; } + void clearBuffers(); + void shiftSendQueueLeft(); + void shiftRecvQueueLeft(); + bool isValidConnection(uint16_t handle, bool requireWaitingForSecurity = false) const; + bool isAdvertising() const; static void onConnect(uint16_t connection_handle); static void onDisconnect(uint16_t connection_handle, uint8_t reason); static void onSecured(uint16_t connection_handle); + static bool onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request); + static void onPairingComplete(uint16_t connection_handle, uint8_t auth_status); + static void onBLEEvent(ble_evt_t* evt); + static void onBleUartRX(uint16_t conn_handle); public: SerialBLEInterface() { _isEnabled = false; _isDeviceConnected = false; - _last_write = 0; + _conn_handle = BLE_CONN_HANDLE_INVALID; + _last_health_check = 0; send_queue_len = 0; + recv_queue_len = 0; } - void startAdv(); - void stopAdv(); void begin(const char* device_name, uint32_t pin_code); - - // BaseSerialInterface methods + void disconnect(); void enable() override; void disable() override; bool isEnabled() const override { return _isEnabled; } - bool isConnected() const override; - bool isWriteBusy() const override; size_t writeFrame(const uint8_t src[], size_t len) override; size_t checkRecvFrame(uint8_t dest[]) override; From 10b43a8f9fa2f1868b2149bdff2d45207b204500 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 5 Dec 2025 12:39:37 +0100 Subject: [PATCH 176/409] variants: XIAO NRF52: Enable button pullup Some versions of the Wio-SX1262 board don't have the button and the pullup resistor populated. Enable the internal pullup to prevent a floating pin and spurious button presses on those boards. This fixes #1173. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 6921892639..f847c65439 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -38,7 +38,7 @@ void XiaoNrf52Board::begin() { digitalWrite(VBAT_ENABLE, HIGH); #ifdef PIN_USER_BTN - pinMode(PIN_USER_BTN, INPUT); + pinMode(PIN_USER_BTN, INPUT_PULLUP); #endif #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) From d834d66803cee513de9f381ecbe6014c67a93d65 Mon Sep 17 00:00:00 2001 From: Christophe Vanlancker Date: Fri, 5 Dec 2025 20:44:56 +0100 Subject: [PATCH 177/409] feat(tdeck): enable GPS support and configure pins --- variants/lilygo_tdeck/platformio.ini | 19 +++++++++++++++++++ variants/lilygo_tdeck/target.cpp | 4 +++- variants/lilygo_tdeck/target.h | 4 +++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tdeck/platformio.ini b/variants/lilygo_tdeck/platformio.ini index adfe9d1e30..807663f8f0 100644 --- a/variants/lilygo_tdeck/platformio.ini +++ b/variants/lilygo_tdeck/platformio.ini @@ -19,6 +19,21 @@ build_flags = -D SX126X_RX_BOOSTED_GAIN=1 -D SX126X_DIO3_TCXO_VOLTAGE=1.8f -D P_LORA_DIO_1=45 ; LORA IRQ pin + -D ENV_INCLUDE_GPS=1 + -D ENV_INCLUDE_AHTX0=0 + -D ENV_INCLUDE_BME280=0 + -D ENV_INCLUDE_BMP280=0 + -D ENV_INCLUDE_SHTC3=0 + -D ENV_INCLUDE_SHT4X=0 + -D ENV_INCLUDE_LPS22HB=0 + -D ENV_INCLUDE_INA3221=0 + -D ENV_INCLUDE_INA219=0 + -D ENV_INCLUDE_INA226=0 + -D ENV_INCLUDE_INA260=0 + -D ENV_INCLUDE_MLX90614=0 + -D ENV_INCLUDE_VL53L0X=0 + -D ENV_INCLUDE_BME680=0 + -D ENV_INCLUDE_BMP085=0 -D P_LORA_NSS=9 ; LORA SS pin -D P_LORA_RESET=17 ; LORA RST pin -D P_LORA_BUSY=13 ; LORA Busy pin @@ -35,8 +50,12 @@ build_flags = -D PIN_TFT_DC=11 -D PIN_TFT_SCL=40 -D PIN_TFT_SDA=41 + -D PIN_GPS_RX=43 + -D PIN_GPS_TX=44 + -D GPS_BAUD_RATE=38400 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tdeck> + + lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} diff --git a/variants/lilygo_tdeck/target.cpp b/variants/lilygo_tdeck/target.cpp index 1120b3ad91..50ffa7359a 100644 --- a/variants/lilygo_tdeck/target.cpp +++ b/variants/lilygo_tdeck/target.cpp @@ -14,7 +14,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +MicroNMEALocationProvider gps(Serial1, &rtc_clock); +EnvironmentSensorManager sensors(gps); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -24,6 +25,7 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); + Wire.begin(18, 8); #if defined(P_LORA_SCLK) return radio.std_init(&spi); diff --git a/variants/lilygo_tdeck/target.h b/variants/lilygo_tdeck/target.h index c803bf2cea..4640925f1a 100644 --- a/variants/lilygo_tdeck/target.h +++ b/variants/lilygo_tdeck/target.h @@ -11,11 +11,13 @@ #include #include #endif +#include "helpers/sensors/EnvironmentSensorManager.h" +#include "helpers/sensors/MicroNMEALocationProvider.h" extern TDeckBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 01eb8716aff657171719c3102e9ba0efdd3eba16 Mon Sep 17 00:00:00 2001 From: Christophe Vanlancker Date: Fri, 5 Dec 2025 20:45:10 +0100 Subject: [PATCH 178/409] fix(core): optimize GPS loop and add display GPIO safeguards --- .../sensors/EnvironmentSensorManager.cpp | 2 +- src/helpers/ui/ST7789LCDDisplay.cpp | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b072bcb0d5..af29bb9958 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -695,8 +695,8 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS + _location->loop(); if (millis() > next_gps_update) { - _location->loop(); if(gps_active){ #ifdef RAK_WISBLOCK_GPS diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 87f9b8ad64..97d82f4292 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -23,9 +23,13 @@ bool ST7789LCDDisplay::begin() { if (!_isOn) { if (_peripher_power) _peripher_power->claim(); - pinMode(PIN_TFT_LEDA_CTL, OUTPUT); - digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - digitalWrite(PIN_TFT_RST, HIGH); + if (PIN_TFT_LEDA_CTL != -1) { + pinMode(PIN_TFT_LEDA_CTL, OUTPUT); + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + } + if (PIN_TFT_RST != -1) { + digitalWrite(PIN_TFT_RST, HIGH); + } // Im not sure if this is just a t-deck problem or not, if your display is slow try this. #ifdef LILYGO_TDECK @@ -54,9 +58,15 @@ void ST7789LCDDisplay::turnOn() { void ST7789LCDDisplay::turnOff() { if (_isOn) { - digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - digitalWrite(PIN_TFT_RST, LOW); - digitalWrite(PIN_TFT_LEDA_CTL, LOW); + if (PIN_TFT_LEDA_CTL != -1) { + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + } + if (PIN_TFT_RST != -1) { + digitalWrite(PIN_TFT_RST, LOW); + } + if (PIN_TFT_LEDA_CTL != -1) { + digitalWrite(PIN_TFT_LEDA_CTL, LOW); + } _isOn = false; if (_peripher_power) _peripher_power->release(); From 638f41d14399ac772aaece7340775cc927175153 Mon Sep 17 00:00:00 2001 From: taco Date: Sat, 6 Dec 2025 16:21:17 +1100 Subject: [PATCH 179/409] calculate shared_secret on demand --- examples/companion_radio/MyMesh.cpp | 2 +- src/helpers/BaseChatMesh.cpp | 24 ++++++++++++++++++------ src/helpers/BaseChatMesh.h | 1 + src/helpers/ContactInfo.h | 3 ++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3aed2da700..9cbb2eba25 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1238,7 +1238,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (_store->saveMainIdentity(identity)) { self_id = identity; writeOKFrame(); - // re-load contacts, to recalc shared secrets + // re-load contacts, to invalidate ecdh shared_secrets resetContacts(); _store->loadContacts(this); } else { diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 4ab3e03bcb..2855c62592 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -113,8 +113,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, from->gps_lon = 0; from->sync_since = 0; - // only need to calculate the shared_secret once, for better performance - self_id.calcSharedSecret(from->shared_secret, id); + from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand } else { MESH_DEBUG_PRINTLN("onAdvertRecv: contacts table is full!"); return; @@ -147,7 +146,7 @@ int BaseChatMesh::searchPeersByHash(const uint8_t* hash) { void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; if (i >= 0 && i < num_contacts) { - // lookup pre-calculated shared_secret + ensureSharedSecretIsValid(contacts[i]); memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); @@ -293,6 +292,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) { // NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY) // override this method in various firmwares, if there's a better strategy + ensureSharedSecretIsValid(contact); mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0); if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay } @@ -342,6 +342,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3 temp[len++] = attempt; // hide attempt number at tail end of payload } + ensureSharedSecretIsValid(recipient); return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, len); } @@ -373,6 +374,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); memcpy(&temp[5], text, text_len + 1); + ensureSharedSecretIsValid(recipient); auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len); if (pkt == NULL) return MSG_SEND_FAILED; @@ -462,6 +464,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, tlen = 4 + len; } + ensureSharedSecretIsValid(recipient); pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen); } if (pkt) { @@ -489,6 +492,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); + ensureSharedSecretIsValid(recipient); pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, 4 + data_len); } if (pkt) { @@ -516,6 +520,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique + ensureSharedSecretIsValid(recipient); pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp)); } if (pkt) { @@ -639,6 +644,7 @@ void BaseChatMesh::checkConnections() { // calc expected ACK reply mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE); + ensureSharedSecretIsValid(*contact); auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9); if (pkt) { sendDirect(pkt, contact->out_path, contact->out_path_len); @@ -703,14 +709,20 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { auto dest = &contacts[num_contacts++]; *dest = contact; - // calc the ECDH shared secret (just once for performance) - self_id.calcSharedSecret(dest->shared_secret, contact.id); - + dest->shared_secret_valid = false; // mark shared_secret as needing calculation return true; // success } return false; } +void BaseChatMesh::ensureSharedSecretIsValid(const ContactInfo& contact) { + if (contact.shared_secret_valid) { + return; // already calculated + } + self_id.calcSharedSecret(contact.shared_secret, contact.id); + contact.shared_secret_valid = true; +} + bool BaseChatMesh::removeContact(ContactInfo& contact) { int idx = 0; while (idx < num_contacts && !contacts[idx].id.matches(contact.id)) { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 76b0dd1cc2..105d2a7951 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -73,6 +73,7 @@ class BaseChatMesh : public mesh::Mesh { mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); + void ensureSharedSecretIsValid(const ContactInfo& contact); protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index 4a8038d3ec..b0b54aef16 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -9,9 +9,10 @@ struct ContactInfo { uint8_t type; // on of ADV_TYPE_* uint8_t flags; int8_t out_path_len; + mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock - uint8_t shared_secret[PUB_KEY_SIZE]; + mutable uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t lastmod; // by OUR clock int32_t gps_lat, gps_lon; // 6 dec places uint32_t sync_since; From d7adcc136b2ad9e4247955854d2451bb16863d47 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 6 Dec 2025 16:49:25 +1100 Subject: [PATCH 180/409] * LPPDataHelpers, readCurrent() signed value --- src/helpers/sensors/LPPDataHelpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/LPPDataHelpers.h b/src/helpers/sensors/LPPDataHelpers.h index b9025de486..37b50f3f12 100644 --- a/src/helpers/sensors/LPPDataHelpers.h +++ b/src/helpers/sensors/LPPDataHelpers.h @@ -113,7 +113,7 @@ class LPPReader { return _pos <= _len; } bool readCurrent(float& amps) { - amps = getFloat(&_buf[_pos], 2, 1000, false); _pos += 2; + amps = getFloat(&_buf[_pos], 2, 1000, true); _pos += 2; return _pos <= _len; } bool readPower(float& watts) { From 676c317f78df09f2f5fed9b499c25e0aa015e722 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 6 Dec 2025 19:17:45 +1100 Subject: [PATCH 181/409] * refactor: on-demand getSharedSecret() --- src/helpers/BaseChatMesh.cpp | 32 ++++++++------------------------ src/helpers/BaseChatMesh.h | 1 - src/helpers/ContactInfo.h | 12 +++++++++++- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 2855c62592..597444fa71 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -146,8 +146,7 @@ int BaseChatMesh::searchPeersByHash(const uint8_t* hash) { void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; if (i >= 0 && i < num_contacts) { - ensureSharedSecretIsValid(contacts[i]); - memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE); + memcpy(dest_secret, contacts[i].getSharedSecret(self_id), PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); } @@ -292,8 +291,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) { // NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY) // override this method in various firmwares, if there's a better strategy - ensureSharedSecretIsValid(contact); - mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0); + mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0); if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay } @@ -342,8 +340,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3 temp[len++] = attempt; // hide attempt number at tail end of payload } - ensureSharedSecretIsValid(recipient); - return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, len); + return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len); } int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) { @@ -374,8 +371,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); memcpy(&temp[5], text, text_len + 1); - ensureSharedSecretIsValid(recipient); - auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len); + auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len); if (pkt == NULL) return MSG_SEND_FAILED; uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -464,8 +460,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, tlen = 4 + len; } - ensureSharedSecretIsValid(recipient); - pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen); + pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, tlen); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -492,8 +487,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); - ensureSharedSecretIsValid(recipient); - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, 4 + data_len); + pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -520,8 +514,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique - ensureSharedSecretIsValid(recipient); - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp)); + pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp)); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -644,8 +637,7 @@ void BaseChatMesh::checkConnections() { // calc expected ACK reply mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE); - ensureSharedSecretIsValid(*contact); - auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9); + auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9); if (pkt) { sendDirect(pkt, contact->out_path, contact->out_path_len); } @@ -715,14 +707,6 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { return false; } -void BaseChatMesh::ensureSharedSecretIsValid(const ContactInfo& contact) { - if (contact.shared_secret_valid) { - return; // already calculated - } - self_id.calcSharedSecret(contact.shared_secret, contact.id); - contact.shared_secret_valid = true; -} - bool BaseChatMesh::removeContact(ContactInfo& contact) { int idx = 0; while (idx < num_contacts && !contacts[idx].id.matches(contact.id)) { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 105d2a7951..76b0dd1cc2 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -73,7 +73,6 @@ class BaseChatMesh : public mesh::Mesh { mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); - void ensureSharedSecretIsValid(const ContactInfo& contact); protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index b0b54aef16..eff07741ab 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -12,8 +12,18 @@ struct ContactInfo { mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock - mutable uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t lastmod; // by OUR clock int32_t gps_lat, gps_lon; // 6 dec places uint32_t sync_since; + + const uint8_t* getSharedSecret(const mesh::LocalIdentity& self_id) const { + if (!shared_secret_valid) { + self_id.calcSharedSecret(shared_secret, id.pub_key); + shared_secret_valid = true; + } + return shared_secret; + } + +private: + mutable uint8_t shared_secret[PUB_KEY_SIZE]; }; From b91b854a1dc5b24c772748bc701c9b08eb7e9e0c Mon Sep 17 00:00:00 2001 From: agessaman Date: Mon, 8 Dec 2025 19:53:33 -0800 Subject: [PATCH 182/409] fix output from LPS22HB: convert barometric pressure from kPa to hPa in EnvironmentSensorManager --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index af29bb9958..2692ec9c85 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -399,7 +399,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen #if ENV_INCLUDE_LPS22HB if (LPS22HB_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure() * 10); // convert kPa to hPa } #endif From 4504ad4daf45dcccf825e4601d33e3a3727b924f Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 12 Dec 2025 19:01:15 +0700 Subject: [PATCH 183/409] 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 5fb1a729ff..39ecd10555 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 11a6a5b41a..eb79405897 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 e566f92932..64c92c43dc 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 0000000000..ee50fe4c6b --- /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 0000000000..1a6f879f37 --- /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 3bec144f78..688a8e886f 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 0f7fc47fa5..bd9287e28d 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 8484085b06..ac3069776b 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 4a061d42fd..8f817ff13b 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 9892638b28..e8c444ed57 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 080387976f..91dfb5abb4 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 4792153a99..79b5610c71 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 8f5b09c9f3..876e3810aa 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 777606a6dd..18aa9e8a45 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 5cedb0f997..7ed1c74b04 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 dc20e5500c..916afefd55 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 7f3a8fea92..2040bf41d7 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 e5104a58d8..fe554fe64f 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 b1e5f8f170..999c7783a1 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 359e5e9a19..d04de5a3e4 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 cffa0aaaff..5920b809a0 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 f04b673f83..6797f62928 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 823acbc7df..ffbc4e9358 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 f37660123d..86be7aa44f 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 14efaf6fd3a2ffd698134e37b4c983c87a50b683 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 29 Nov 2025 10:55:01 +0100 Subject: [PATCH 184/409] thinknode_m6: initial port --- boards/thinknode_m6.json | 72 +++++++++++++ variants/thinknode_m5/platformio.ini | 2 +- variants/thinknode_m6/ThinkNodeM6Board.cpp | 95 ++++++++++++++++ variants/thinknode_m6/ThinkNodeM6Board.h | 56 ++++++++++ variants/thinknode_m6/platformio.ini | 120 +++++++++++++++++++++ variants/thinknode_m6/target.cpp | 49 +++++++++ variants/thinknode_m6/target.h | 31 ++++++ variants/thinknode_m6/variant.cpp | 35 ++++++ variants/thinknode_m6/variant.h | 108 +++++++++++++++++++ 9 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 boards/thinknode_m6.json create mode 100644 variants/thinknode_m6/ThinkNodeM6Board.cpp create mode 100644 variants/thinknode_m6/ThinkNodeM6Board.h create mode 100644 variants/thinknode_m6/platformio.ini create mode 100644 variants/thinknode_m6/target.cpp create mode 100644 variants/thinknode_m6/target.h create mode 100644 variants/thinknode_m6/variant.cpp create mode 100644 variants/thinknode_m6/variant.h diff --git a/boards/thinknode_m6.json b/boards/thinknode_m6.json new file mode 100644 index 0000000000..1f91b9aa33 --- /dev/null +++ b/boards/thinknode_m6.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ] + ], + "usb_product": "elecrow_solar", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M6", + "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 solar", + "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" +} diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 23db506a61..cf9c9d4185 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,7 +3,7 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 - -I src/helpres/sensors + -I src/helpers/sensors -D THINKNODE_M5 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 diff --git a/variants/thinknode_m6/ThinkNodeM6Board.cpp b/variants/thinknode_m6/ThinkNodeM6Board.cpp new file mode 100644 index 0000000000..1ccc202661 --- /dev/null +++ b/variants/thinknode_m6/ThinkNodeM6Board.cpp @@ -0,0 +1,95 @@ +#include "ThinkNodeM6Board.h" +#include + +#ifdef THINKNODE_M6 + +#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 ThinkNodeM6Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, LOW); +#endif + + delay(10); // give sx1262 some time to power up +} + +uint16_t ThinkNodeM6Board::getBattMilliVolts() { + int adcvalue = 0; + + digitalWrite(PIN_ADC_CTRL, HIGH); + analogReference(AR_INTERNAL_3_0); + analogReadResolution(12); + delay(10); + + // ADC range is 0..3000mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + digitalWrite(PIN_ADC_CTRL, LOW); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); +} + +bool ThinkNodeM6Board::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_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h new file mode 100644 index 0000000000..c3d7dad686 --- /dev/null +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +// built-ins +#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 + +#define VBAT_DIVIDER_COMP ADC_MULTIPLIER // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ BATTERY_PIN +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class ThinkNodeM6Board : public mesh::MainBoard { +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 + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } + #endif + + const char* getManufacturerName() const override { + return "Elecrow ThinkNode-M6"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + void powerOff() override { + + // turn off all leds, sd_power_system_off will not do this for us + #ifdef P_LORA_TX_LED + digitalWrite(P_LORA_TX_LED, LOW); + #endif + + // power off board + sd_power_system_off(); + + } +}; diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini new file mode 100644 index 0000000000..16394ced9c --- /dev/null +++ b/variants/thinknode_m6/platformio.ini @@ -0,0 +1,120 @@ +[ThinkNode_M6] +extends = nrf52_base +board = thinknode_m6 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + ${sensor_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_m6 + -D THINKNODE_M6=1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=38 + -D P_LORA_NSS=44 + -D P_LORA_RESET=42 + -D P_LORA_BUSY=43 + -D P_LORA_SCLK=45 + -D P_LORA_MISO=47 + -D P_LORA_MOSI=46 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=PIN_LED_BLUE +; -D PERSISTANT_GPS=1 +; -D ENV_SKIP_GPS_DETECT=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + + + + + +<../variants/thinknode_m6> +lib_deps = + ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} +debug_tool = jlink +upload_protocol = nrfutil + +[env:ThinkNode_M6_repeater] +extends = ThinkNode_M6 +build_flags = + ${ThinkNode_M6.build_flags} + -D ADVERT_NAME='"ThinkNode 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 + -D GPS_NMEA_DEBUG=1 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<../examples/simple_repeater/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + +[env:ThinkNode_M6_room_server] +extends = ThinkNode_M6 +build_flags = + ${ThinkNode_M6.build_flags} + -D ADVERT_NAME='"ThinkNode Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<../examples/simple_room_server/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + +[env:ThinkNode_M6_companion_radio_ble] +extends = ThinkNode_M6 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${ThinkNode_M6.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D QSPIFLASH=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +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 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M6_companion_radio_usb] +extends = ThinkNode_M6 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${ThinkNode_M6.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D QSPIFLASH=1 + -D OFFLINE_QUEUE_SIZE=256 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +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 + end2endzone/NonBlockingRTTTL@^1.3.0 \ No newline at end of file diff --git a/variants/thinknode_m6/target.cpp b/variants/thinknode_m6/target.cpp new file mode 100644 index 0000000000..c14dd300f8 --- /dev/null +++ b/variants/thinknode_m6/target.cpp @@ -0,0 +1,49 @@ +#include +#include "target.h" +#include +#include + +ThinkNodeM6Board 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, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/thinknode_m6/target.h b/variants/thinknode_m6/target.h new file mode 100644 index 0000000000..38b1fed1b6 --- /dev/null +++ b/variants/thinknode_m6/target.h @@ -0,0 +1,31 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ThinkNodeM6Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m6/variant.cpp b/variants/thinknode_m6/variant.cpp new file mode 100644 index 0000000000..c88f387db6 --- /dev/null +++ b/variants/thinknode_m6/variant.cpp @@ -0,0 +1,35 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() { + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(QSPI_FLASH_EN, OUTPUT); + digitalWrite(QSPI_FLASH_EN, HIGH); + + // For now stick adc_ctrl to fixed value + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); + + pinMode(PIN_LED_RED, OUTPUT); + pinMode(PIN_LED_BLUE, OUTPUT); + digitalWrite(PIN_LED_BLUE, LOW); + digitalWrite(PIN_LED_RED, LOW); + + // gps + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, HIGH); + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, HIGH); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, HIGH); +} diff --git a/variants/thinknode_m6/variant.h b/variants/thinknode_m6/variant.h new file mode 100644 index 0000000000..70fd65062c --- /dev/null +++ b/variants/thinknode_m6/variant.h @@ -0,0 +1,108 @@ +/* + * 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 WIRE_INTERFACES_COUNT (1) +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define PIN_PWR_EN (27) + +#define BATTERY_PIN (28) +#define ADC_MULTIPLIER (1.75F) +#define PIN_ADC_CTRL (11) + +#define ADC_RESOLUTION (12) +#define BATTERY_SENSE_RES (12) + +#define AREF_VOLTAGE (3.0) + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +#define PIN_SERIAL2_RX (22) +#define PIN_SERIAL2_TX (24) + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define PIN_WIRE_SDA (41) // P1.9 +#define PIN_WIRE_SCL (8) // P0.8 + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (47) +#define PIN_SPI_MOSI (46) +#define PIN_SPI_SCK (45) +//#define PIN_SPI_NSS (24) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define PIN_LED_RED (12) +#define PIN_LED_BLUE (7) +#define LED_BLUE (-1) + +#define LED_BUILTIN PIN_LED_BLUE +#define PIN_LED LED_BUILTIN +#define LED_PIN LED_BUILTIN +#define LED_STATE_ON HIGH + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (17) +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN + +//////////////////////////////////////////////////////////////////////////////// +// QSPI + +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +#define PIN_QSPI_SCK (35) +#define PIN_QSPI_CS (23) +#define PIN_QSPI_IO0 (33) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (34) // MISO if using two bit interface +#define PIN_QSPI_IO2 (36) // WP if using two bit interface (i.e. not used) +#define PIN_QSPI_IO3 (37) // HOLD if using two bit interface (i.e. not used) +#define QSPI_FLASH_EN (21) + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define GPS_L76K +#define PIN_GPS_RX (2) +#define PIN_GPS_TX (3) +#define PIN_GPS_EN (6) // EN +#define PIN_GPS_RESET (29) +#define PIN_GPS_STANDBY (30) // STANDBY +#define PIN_GPS_PPS (31) +#define GPS_BAUD_RATE 9600 From bde4fc3a231f53a4ba95ca2a34ff23509ab60ac0 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 22:04:24 +0100 Subject: [PATCH 185/409] 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 0000000000..617740b61e --- /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 0000000000..74019fcbb6 --- /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 0000000000..c9b962737b --- /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 0000000000..8ef2ba54ad --- /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 0000000000..c6708e4d13 --- /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 0000000000..f60a85b032 --- /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 0000000000..dad0f3f550 --- /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 0000000000..02ed78a84f --- /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 0df8c86b98119eb667e22290897ceb14a3023786 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 186/409] 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 b734fe6b94..fcde504844 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 2deb9cf1440a658c0b51cb824c6582eed7fb87f1 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 13 Dec 2025 07:32:26 +0700 Subject: [PATCH 187/409] 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 39ecd10555..6ae6ac0a8b 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 6486192477f519dc2e93b86bfde7c4bbeab133d5 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 17 Dec 2025 10:22:15 +0100 Subject: [PATCH 188/409] 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 9dfc3833ea..58f9e39996 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 87b0e432bbd1796aff43e506969151c07b245052 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 14:31:55 +0100 Subject: [PATCH 189/409] 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 1a6f879f37..5158229d0e 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 688a8e886f..24c5a812e4 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 bd9287e28d..e35afeb19e 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 58f9e39996..87f8598043 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 ac3069776b..2d185365fd 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 8f817ff13b..1a9b00c4c5 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 e8c444ed57..2e720b72bd 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 91dfb5abb4..316536aed6 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 79b5610c71..db800951fd 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 876e3810aa..fd9c819ebf 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 18aa9e8a45..ba49649197 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 7ed1c74b04..1e8dbce479 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 916afefd55..5513701042 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 2040bf41d7..8922cc31a4 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 fe554fe64f..732edc69f8 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 999c7783a1..f14f2f1aa7 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 d04de5a3e4..02aa07330d 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 5920b809a0..b6eff743fe 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 6797f62928..b51f56bdbb 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 ffbc4e9358..5d36834ba7 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 86be7aa44f..32dcc576ab 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 93d1560d1471d765be4b94e65623db38e69d80a3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 15:00:08 +0100 Subject: [PATCH 190/409] 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 5158229d0e..c349a8e7ac 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 54929cd1e8..ad5f1333e7 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 24c5a812e4..0ccbb1275f 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 f46a1a84bc..4730a49b0c 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 e35afeb19e..a040e37fbe 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 44940b8ff4..bf3f4a9e8d 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 87f8598043..25cf643c5c 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 ee7996921c..15507ad31c 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 2d185365fd..e6da3d1153 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 6b660383a7..1adc14bd6c 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 1a9b00c4c5..78cb8a7fdb 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 46bff1fc2e..a61909fe4e 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 2e720b72bd..e98972ae2e 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 dee1468807..e125964452 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 316536aed6..b7650d0074 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 dee1468807..e125964452 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 db800951fd..c92bcfca4f 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 0d0e8993cd..0fa7d5f941 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 fd9c819ebf..0f4d2c8b5b 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 561ed5049d..c38f71e45b 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 ba49649197..34612e93ee 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 9a27828715..6a749aab6a 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 1e8dbce479..6961fc86d7 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 b923e16e52..4ac7207217 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 5513701042..ba00213465 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 97a9660227..1a37db156a 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 8922cc31a4..394b05fb00 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 68638f0dde..11b9f0a51f 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 732edc69f8..032b00af4f 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 d6c044d1d6..1493d22a12 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 f14f2f1aa7..eab08163bb 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 a41abd92ed..27432b1b4c 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 02aa07330d..c16d8ccd71 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 12dd73624c..bfec5f3fba 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 b6eff743fe..7e961f0039 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 34d9a87455..d07df83f92 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 b51f56bdbb..e1930ff792 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 153d476c42..cb54609d48 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 5d36834ba7..9b4e153b48 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 f847c65439..34c157677c 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 32dcc576ab..1f1490cf2b 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 e3bb225efb158cb0d757edb14bd7c56205e347db Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 17:05:33 +0100 Subject: [PATCH 191/409] 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 ee50fe4c6b..93dfb2881b 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 c349a8e7ac..588c190a19 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 11b9f0a51f..88d09249bb 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 032b00af4f..d62dee854c 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 27432b1b4c..9211b4c648 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 c16d8ccd71..32f54bb381 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 d07df83f92..fbf64f77a5 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 e1930ff792..7e82e4477f 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 cb54609d48..d0ab97858e 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 9b4e153b48..697028b063 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 34c157677c..e61f38066a 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 1f1490cf2b..0732549398 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 b024b9e1a14db4309692972f6d2cfca53c4020dd Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 19:56:00 +0100 Subject: [PATCH 192/409] 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 93dfb2881b..c0d58314e2 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 588c190a19..c5f32a2049 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 ad5f1333e7..bc955fb536 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 0ccbb1275f..69437c8778 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 4730a49b0c..4995e7de1c 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 a040e37fbe..74e26455c6 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 bf3f4a9e8d..f1d9f17d3a 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 25cf643c5c..acb58b3ce7 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 15507ad31c..b963b2f4b5 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 e6da3d1153..c7e96921ec 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 1adc14bd6c..1c416039d0 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 78cb8a7fdb..00a26029c3 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 a61909fe4e..2f1fa5f9af 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 e98972ae2e..a9844c90b0 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 e125964452..81d3d0c9af 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 b7650d0074..c9bdc229be 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 e125964452..81d3d0c9af 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 c92bcfca4f..8e6974bd35 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 0fa7d5f941..0c53121f2f 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 0f4d2c8b5b..db65c99b18 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 c38f71e45b..0267185c9c 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 34612e93ee..805f9dfa87 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 4ac7207217..7011521b8f 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 ba00213465..c23ed1c99c 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 1a37db156a..cb7d193085 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 394b05fb00..b329cdeb8b 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 88d09249bb..7eab4bc9ed 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 d62dee854c..9aa457d248 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 1493d22a12..c088303591 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 eab08163bb..dfe007bf7f 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 9211b4c648..0d390f798e 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 bfec5f3fba..45449bafc2 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 7e961f0039..500a02658a 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 fbf64f77a5..4153714e93 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 7e82e4477f..bfd87d3963 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 e61f38066a..b7b60dc63d 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 0732549398..1c46dfeee6 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 22b1585959c1dfd265f157ac2d146cca1753b07b Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Wed, 17 Dec 2025 10:35:47 +0100 Subject: [PATCH 193/409] 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 c5f32a2049..0d6c0a4316 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 8eb229bcf8aa665f1b7f6aa1668aeb616b6b0843 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 9 Dec 2025 20:58:11 +0100 Subject: [PATCH 194/409] 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 cb7d193085..65c54711da 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 b329cdeb8b..a181256b0d 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 cba29ea50c6cdbb79f31f5f65902f10bee035ccf 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 195/409] 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 b4811a2076..eb1e90bb70 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 557b86c5e1..25968d78fd 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 e855706abb633a1cd7ee791cd49d92481576d2de 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 196/409] 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 29cc59d332..8077627f8b 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 6c993827de266a9c81c3f4f6f3dc92737bde5aff 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 197/409] 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 a5b443cf25..f02541382d 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 2a6380d5db..82d958b5d4 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 f17b3a8df1..a598e3cad1 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 cc28b1a34d19d5f9254b7e443ca7ba5baa239b9b 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 198/409] 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 2692ec9c85..77a791bdee 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 245a818085c02167698149a456078f4795cf21fb 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 199/409] 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 9014743a32..e340782112 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 5c6c15942bda590f02193509c496b8e3c71f3e94 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 23 Dec 2025 12:48:08 +0700 Subject: [PATCH 200/409] 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 6ae6ac0a8b..326417a44c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1115,3 +1115,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 ed9f0c5fce..6dc4792e93 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 7387e77e73..d67a7029c0 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 eb79405897..718660d3bd 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 a3de990aa7..af27a9021d 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 068783ab1a..ca1a56128f 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 64c92c43dc..d49feddf63 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: @@ -47,6 +49,27 @@ class ESP32Board : public mesh::MainBoard { return temperatureRead(); } + 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 1706f759b74ff3adb6c514d120e5b2d0c06951c5 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Dec 2025 11:00:34 +0700 Subject: [PATCH 201/409] 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 326417a44c..e86d1ef060 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1116,7 +1116,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 6dc4792e93..343aa44f58 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 d67a7029c0..8c745613e7 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 89a289eb222e106cde4434d0f48fd57e602feac2 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Dec 2025 11:23:19 +0700 Subject: [PATCH 202/409] 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 af27a9021d..43a49ac43d 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 ca1a56128f..642a4cce3a 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 0d11a02e71588e5bda9690b0fc93869e22785876 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Dec 2025 11:47:19 +0700 Subject: [PATCH 203/409] 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 d49feddf63..cfc403cd03 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -50,7 +50,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 def1902688d27a3bf2f1c1dcac914aa8b845bd19 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 24 Dec 2025 12:04:39 +0700 Subject: [PATCH 204/409] 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 74baebc3a6..4ff9555103 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 26321162eeaa3a158285a4175254ea9530210614 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 27 Dec 2025 15:23:23 +0700 Subject: [PATCH 205/409] 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 6ae6ac0a8b..993b277967 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 0b30d2433f014fec50037b5d49ec4cbfa6b3cc22 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 27 Dec 2025 15:25:21 +0700 Subject: [PATCH 206/409] 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 64c92c43dc..61f814fd95 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 90d1e87ba1043663283ea6eb8a55c6392aaba637 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 27 Dec 2025 20:46:28 +1100 Subject: [PATCH 207/409] * 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 1bda9e9626..0548c9073d 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 992d971f07e319c95397e98330636bc0682c61fe 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 208/409] 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 78ea5fa1e4..15bb5ce673 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 33b1e7edb9635544ce3761fb7d1079824b9acc79 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Mon, 29 Dec 2025 21:49:13 +0700 Subject: [PATCH 209/409] 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 43a49ac43d..78e1b5e0bb 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 d911a34eeb959c566d7d1c4751eb361db5b7b24f Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Mon, 29 Dec 2025 22:38:05 +0700 Subject: [PATCH 210/409] 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 cfc403cd03..5d28b6daed 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 { @@ -65,8 +65,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 4a869163b27b013cf46ff807143b1e1c4ebdf158 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 211/409] 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 9cbb2eba25..56894ddebb 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 7ea751d3a0c3307bb5afeb337a05b4d687f08e56 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 31 Dec 2025 13:01:56 +0100 Subject: [PATCH 212/409] Add venv dirs to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index db044b5adf..50631d890c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ cmake-* .cache .ccls compile_commands.json +.venv/ +venv/ From e79ee118722fc0050c1de0378bb5ac93fc7084e6 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 16 Dec 2025 20:27:48 +0100 Subject: [PATCH 213/409] 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 77a791bdee..f0bb5654b5 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 ab7935142cd257c860f58671dc849dfb047338b8 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Tue, 16 Dec 2025 20:28:20 +0100 Subject: [PATCH 214/409] 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 f0bb5654b5..b7238def9c 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 813e50297051a12d7ef525bfcf1b24ea057352a5 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 2 Jan 2026 12:54:57 +1100 Subject: [PATCH 215/409] * 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 0000000000..ceedbbf057 --- /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 faf177de467d124485ff5e7710cc2c46e216a4aa 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 216/409] 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 7f5761f3cb..4faac97549 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 9cbb2eba25..19a92f7757 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1613,6 +1613,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 3af25495bb4e2eaf17de4a916ce9355e62e71f31 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 3 Jan 2026 12:02:15 +1100 Subject: [PATCH 217/409] * Repeater: new anon request sub-type: ANON_REQ_TYPE_REGIONS (rate limited to max 4 every 3 mins) * Companion: new CMD_SEND_ANON_REQ command (reply with existing RESP_CODE_SENT frame) --- examples/companion_radio/MyMesh.cpp | 22 ++++++++++++++++++ examples/simple_repeater/MyMesh.cpp | 22 +++++++++++++++--- examples/simple_repeater/MyMesh.h | 3 ++- src/helpers/BaseChatMesh.cpp | 25 ++++++++++++++++++++ src/helpers/BaseChatMesh.h | 1 + src/helpers/RegionMap.cpp | 36 ++++++++++++++++++++++++----- src/helpers/RegionMap.h | 7 ++++-- 7 files changed, 104 insertions(+), 12 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7689708c0d..59a0078f44 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -53,6 +53,7 @@ #define CMD_SET_FLOOD_SCOPE 54 // v8+ #define CMD_SEND_CONTROL_DATA 55 // v8+ #define CMD_GET_STATS 56 // v8+, second byte is stats type +#define CMD_SEND_ANON_REQ 57 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -1286,6 +1287,27 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found } + } else if (cmd_frame[0] == CMD_SEND_ANON_REQ && len > 1 + PUB_KEY_SIZE) { + uint8_t *pub_key = &cmd_frame[1]; + ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); + uint8_t *data = &cmd_frame[1 + PUB_KEY_SIZE]; + if (recipient) { + uint32_t tag, est_timeout; + int result = sendAnonReq(*recipient, data, len - (1 + PUB_KEY_SIZE), tag, est_timeout); + if (result == MSG_SEND_FAILED) { + writeErrFrame(ERR_CODE_TABLE_FULL); + } else { + clearPendingReqs(); + pending_req = tag; // match this to onContactResponse() + out_frame[0] = RESP_CODE_SENT; + out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; + memcpy(&out_frame[2], &tag, 4); + memcpy(&out_frame[6], &est_timeout, 4); + _serial->writeFrame(out_frame, 10); + } + } else { + writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found + } } else if (cmd_frame[0] == CMD_SEND_STATUS_REQ && len >= 1 + PUB_KEY_SIZE) { uint8_t *pub_key = &cmd_frame[1]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 33e32a68a6..253fa701dc 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -51,6 +51,8 @@ #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ +#define ANON_REQ_TYPE_REGIONS 0x01 + #define CLI_REPLY_DELAY_MILLIS 600 #define LAZY_CONTACTS_WRITE_DELAY 5000 @@ -139,6 +141,19 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr return 13; // reply length } +uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + // REVISIT: should there be params like 'since' in request data[] ? + if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + + return 8 + region_map.exportNamesTo((char *) &reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -450,8 +465,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); - //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes - // TODO + } else if (data[4] == ANON_REQ_TYPE_REGIONS) { + reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown request type } @@ -668,7 +683,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), - discover_limiter(4, 120) // max 4 every 2 minutes + discover_limiter(4, 120), // max 4 every 2 minutes + regions_limiter(4, 180) // max 4 every 3 minutes #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 343aa44f58..172c756b9c 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -93,7 +93,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionMap region_map, temp_map; RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; - RateLimiter discover_limiter; + RateLimiter discover_limiter, regions_limiter; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS @@ -114,6 +114,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); + uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 597444fa71..0818562895 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -477,6 +477,31 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, return MSG_SEND_FAILED; } +int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout) { + mesh::Packet* pkt; + { + uint8_t temp[MAX_PACKET_PAYLOAD]; + tag = getRTCClock()->getCurrentTimeUnique(); + memcpy(temp, &tag, 4); // tag to match later (also extra blob to help make packet_hash unique) + memcpy(&temp[4], data, len); + + pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + len); + } + if (pkt) { + uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); + if (recipient.out_path_len < 0) { + sendFloodScoped(recipient, pkt); + est_timeout = calcFloodTimeoutMillisFor(t); + return MSG_SEND_SENT_FLOOD; + } else { + sendDirect(pkt, recipient.out_path, recipient.out_path_len); + est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); + return MSG_SEND_SENT_DIRECT; + } + } + return MSG_SEND_FAILED; +} + int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout) { if (data_len > MAX_PACKET_PAYLOAD - 16) return MSG_SEND_FAILED; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 76b0dd1cc2..40818fed6e 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -141,6 +141,7 @@ class BaseChatMesh : public mesh::Mesh { int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout); bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len); int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout); + int sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout); int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout); int sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout); bool shareContactZeroHop(const ContactInfo& contact); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 368446157b..e227532af4 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -24,12 +24,12 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { #endif } -bool RegionMap::load(FILESYSTEM* _fs) { - if (_fs->exists("/regions2")) { +bool RegionMap::load(FILESYSTEM* _fs, const char* path) { + if (_fs->exists(path ? path : "/regions2")) { #if defined(RP2040_PLATFORM) - File file = _fs->open("/regions2", "r"); + File file = _fs->open(path ? path : "/regions2", "r"); #else - File file = _fs->open("/regions2"); + File file = _fs->open(path ? path : "/regions2"); #endif if (file) { @@ -67,8 +67,8 @@ bool RegionMap::load(FILESYSTEM* _fs) { return false; // failed } -bool RegionMap::save(FILESYSTEM* _fs) { - File file = openWrite(_fs, "/regions2"); +bool RegionMap::save(FILESYSTEM* _fs, const char* path) { + File file = openWrite(_fs, path ? path : "/regions2"); if (file) { uint8_t pad[128]; memset(pad, 0, sizeof(pad)); @@ -235,3 +235,27 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& void RegionMap::exportTo(Stream& out) const { printChildRegions(0, &wildcard, out); // recursive } + +int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) { + char *dp = dest; + if ((wildcard.flags & mask) == 0) { + *dp++ = '*'; + *dp++ = ','; + } + + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param) + int len = strlen(region->name); + if ((dp - dest) + len + 2 < max_len) { // only append if name will fit + memcpy(dp, region->name, len); + dp += len; + *dp++ = ','; + } + } + } + if (dp > dest) { dp--; } // don't include trailing comma + + *dp = 0; // set null terminator + return dp - dest; // return length +} diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 50513be1c5..dcd9a77452 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -32,8 +32,8 @@ class RegionMap { static bool is_name_char(char c); - bool load(FILESYSTEM* _fs); - bool save(FILESYSTEM* _fs); + bool load(FILESYSTEM* _fs, const char* path=NULL); + bool save(FILESYSTEM* _fs, const char* path=NULL); RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0); RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); @@ -47,6 +47,9 @@ class RegionMap { bool clear(); void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; } int getCount() const { return num_regions; } + const RegionEntry* getByIdx(int i) const { return ®ions[i]; } + const RegionEntry* getRoot() const { return &wildcard; } + int exportNamesTo(char *dest, int max_len, uint8_t mask); void exportTo(Stream& out) const; }; From ed263b07270096ebf8062d155052c3a9d727beb3 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 3 Jan 2026 15:39:57 +1300 Subject: [PATCH 218/409] 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 2df9980af7..9470c82717 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 2b6c6edd5b..c7139b400c 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 71bb49e556d11e2fb4276c4346d901f125ff1850 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sat, 3 Jan 2026 16:36:19 +1300 Subject: [PATCH 219/409] 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 9470c82717..de5cce7846 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 c7139b400c..19291497fe 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 63ae92aa0973d312b802ef938eb44f1414731c82 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Sat, 3 Jan 2026 16:32:36 +0100 Subject: [PATCH 220/409] 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 16394ced9c..db22073c0a 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 63767cdb7de6dcdec7eadadf3cf16716b3d3f88e Mon Sep 17 00:00:00 2001 From: cj-vana Date: Sat, 3 Jan 2026 18:54:48 -0700 Subject: [PATCH 221/409] 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 73fa960c1a..d3bcbbef9d 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 2e536de5ea..e4332013ad 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 8708fa012ad9f64f1b7bc1fa93ef466378f4f496 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Sun, 4 Jan 2026 17:43:25 +1300 Subject: [PATCH 222/409] 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 de5cce7846..462e3ecc30 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 818f5e9da507840be3753eb7bffb08a23546f286 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 223/409] 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 8979edc25a..22bb4090a4 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 d4a2e5789f0de675a84589fb0cdeaee451929bb1 Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 6 Jan 2026 14:49:15 +0000 Subject: [PATCH 224/409] 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 36c6386f67..dcb2873c0b 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 c26a5bc69d..ecfd7889b1 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 a7a6bb51ce867da45851583f8f50bf0a64abfeaf 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 225/409] 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 61ccd4f4be..36de671e25 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 539cc52e83..f8cc936083 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 9ef9734e59..f27f57fd9e 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 e18bcde161..0d3d46523f 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 b3ba2a8634..1428221d0a 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 55fc03b1090251ae4dac5070ee09ed1a1cfd663b Mon Sep 17 00:00:00 2001 From: Ferdia McKeogh Date: Tue, 6 Jan 2026 20:11:19 +0100 Subject: [PATCH 226/409] 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 32f54bb381..f26bdf02ef 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 +}; From 5cc44dd802b07a4f4180e8ba694fabcb7de96174 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 8 Jan 2026 13:20:52 +1100 Subject: [PATCH 227/409] * ANON_REQ_TYPE_REGIONS now direct only, with reply_path encoded in request --- examples/simple_repeater/MyMesh.cpp | 18 ++++++++++++++---- examples/simple_repeater/MyMesh.h | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 253fa701dc..261184c577 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -142,8 +142,13 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { - // REVISIT: should there be params like 'since' in request data[] ? if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + // other params?? + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -463,12 +468,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; + + reply_path_len = -1; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); - } else if (data[4] == ANON_REQ_TYPE_REGIONS) { + } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else { - reply_len = 0; // unknown request type + reply_len = 0; // unknown/invalid request type } if (reply_len == 0) return; // invalid request @@ -478,9 +485,12 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); - } else { + } else if (reply_path_len < 0) { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); + } else { + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); } } } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 172c756b9c..49952770db 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -88,6 +88,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { NodePrefs _prefs; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; + uint8_t reply_path[MAX_PATH_SIZE]; + int8_t reply_path_len; ClientACL acl; TransportKeyStore key_store; RegionMap region_map, temp_map; From fa48d4fe815bbbebbdffa2804dfca450ffe11eec Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 22 Dec 2025 16:21:44 +0100 Subject: [PATCH 228/409] variants: Nano G2 Ultra: Use common implementation of startOTAUpdate() Signed-off-by: Frieder Schrempf --- variants/nano_g2_ultra/nano-g2.cpp | 61 ------------------------------ variants/nano_g2_ultra/nano-g2.h | 4 +- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/variants/nano_g2_ultra/nano-g2.cpp b/variants/nano_g2_ultra/nano-g2.cpp index 6a749aab6a..23695845f4 100644 --- a/variants/nano_g2_ultra/nano-g2.cpp +++ b/variants/nano_g2_ultra/nano-g2.cpp @@ -3,25 +3,8 @@ #ifdef NANO_G2_ULTRA -#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 NanoG2Ultra::begin() { NRF52Board::begin(); @@ -56,48 +39,4 @@ uint16_t NanoG2Ultra::getBattMilliVolts() // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool NanoG2Ultra::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("NANO_G2_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/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 6961fc86d7..4f7eb0741d 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -35,11 +35,11 @@ #define PIN_VBAT_READ (0 + 2) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class NanoG2Ultra : public NRF52Board { +class NanoG2Ultra : public NRF52BoardOTA { public: + NanoG2Ultra() : NRF52BoardOTA("NANO_G2_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char *id, char reply[]) override; const char *getManufacturerName() const override { return "Nano G2 Ultra"; } From 57fa1ba8542829747c4d3e752c043033ed26e51e Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 22 Dec 2025 16:22:54 +0100 Subject: [PATCH 229/409] variants: Wio WM1110: Use common implementation of startOTAUpdate() Signed-off-by: Frieder Schrempf --- variants/wio_wm1110/WioWM1110Board.cpp | 45 ++------------------------ variants/wio_wm1110/WioWM1110Board.h | 5 ++- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index d0ab97858e..2825e55433 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -1,24 +1,9 @@ #ifdef WIO_WM1110 -#include -#include -#include - #include "WioWM1110Board.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"); -} +#include +#include void WioWM1110Board::begin() { NRF52BoardDCDC::begin(); @@ -42,31 +27,5 @@ void WioWM1110Board::begin() { delay(10); } - -bool WioWM1110Board::startOTAUpdate(const char *id, char reply[]) { - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - Bluefruit.setTxPower(4); - Bluefruit.setName("WM1110_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - bledfu.begin(); - - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); - Bluefruit.Advertising.setFastTimeout(30); - Bluefruit.Advertising.start(0); - - strcpy(reply, "OK - started"); - return true; -} - #endif diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index 697028b063..adcea87765 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -11,8 +11,9 @@ #endif #define Serial Serial1 -class WioWM1110Board : public NRF52BoardDCDC { +class WioWM1110Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: + WioWM1110Board() : NRF52BoardOTA("WM1110_OTA") {} void begin(); #if defined(LED_GREEN) @@ -37,8 +38,6 @@ class WioWM1110Board : public NRF52BoardDCDC { return "Seeed Wio WM1110"; } - bool startOTAUpdate(const char* id, char reply[]) override; - void enableSensorPower(bool enable) { digitalWrite(SENSOR_POWER_PIN, enable ? HIGH : LOW); if (enable) { From 578d55b28ab48a06b2fb0bc348e3558b0a9f54f3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 22 Dec 2025 16:25:35 +0100 Subject: [PATCH 230/409] variants: Thinknode M3/M6: Use common Nrf52Board class Signed-off-by: Frieder Schrempf --- variants/thinknode_m3/ThinknodeM3Board.cpp | 70 +--------------------- variants/thinknode_m3/ThinknodeM3Board.h | 26 +++----- variants/thinknode_m6/ThinkNodeM6Board.cpp | 61 +------------------ variants/thinknode_m6/ThinkNodeM6Board.h | 22 ++----- 4 files changed, 16 insertions(+), 163 deletions(-) diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinknodeM3Board.cpp index 74019fcbb6..d7ecd62d57 100644 --- a/variants/thinknode_m3/ThinknodeM3Board.cpp +++ b/variants/thinknode_m3/ThinknodeM3Board.cpp @@ -5,76 +5,10 @@ #include void ThinknodeM3Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + Nrf52BoardDCDC::begin(); 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 +} \ No newline at end of file diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinknodeM3Board.h index c9b962737b..6269408758 100644 --- a/variants/thinknode_m3/ThinknodeM3Board.h +++ b/variants/thinknode_m3/ThinknodeM3Board.h @@ -1,13 +1,13 @@ #pragma once -#include #include +#include +#include #define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX) -class ThinknodeM3Board : public mesh::MainBoard { +class ThinknodeM3Board : public Nrf52BoardDCDC { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: @@ -27,12 +27,10 @@ class ThinknodeM3Board : public mesh::MainBoard { 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 +#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 } @@ -56,13 +54,5 @@ class ThinknodeM3Board : public mesh::MainBoard { return 0; } - void powerOff() override { - sd_power_system_off(); - } - - void reboot() override { - NVIC_SystemReset(); - } - -// bool startOTAUpdate(const char* id, char reply[]) override; + void powerOff() override { sd_power_system_off(); } }; \ No newline at end of file diff --git a/variants/thinknode_m6/ThinkNodeM6Board.cpp b/variants/thinknode_m6/ThinkNodeM6Board.cpp index 1ccc202661..8ebae64c64 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.cpp +++ b/variants/thinknode_m6/ThinkNodeM6Board.cpp @@ -4,25 +4,9 @@ #ifdef THINKNODE_M6 #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 ThinkNodeM6Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Wire.begin(); @@ -49,47 +33,4 @@ uint16_t ThinkNodeM6Board::getBattMilliVolts() { // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool ThinkNodeM6Board::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_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h index c3d7dad686..c03e1fbc60 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.h +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 @@ -11,21 +12,13 @@ #define PIN_VBAT_READ BATTERY_PIN #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM6Board : public mesh::MainBoard { -protected: - uint8_t startup_reason; - +class ThinkNodeM6Board : public Nrf52BoardOTA { public: - + ThinkNodeM6Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {} 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) +#if defined(P_LORA_TX_LED) void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on } @@ -38,10 +31,6 @@ class ThinkNodeM6Board : public mesh::MainBoard { return "Elecrow ThinkNode-M6"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { // turn off all leds, sd_power_system_off will not do this for us @@ -51,6 +40,5 @@ class ThinkNodeM6Board : public mesh::MainBoard { // power off board sd_power_system_off(); - } }; From 24a4b99e314595a20fa160b3d40d9552c4138a12 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 19 Dec 2025 11:35:27 +0100 Subject: [PATCH 231/409] variants: Heltec Mesh Solar: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf --- variants/heltec_mesh_solar/MeshSolarBoard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 69437c8778..92d69f3ae4 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -20,7 +20,7 @@ #define SX126X_DIO2_AS_RF_SWITCH true #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -class MeshSolarBoard : public NRF52BoardOTA { +class MeshSolarBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: MeshSolarBoard() : NRF52BoardOTA("MESH_SOLAR_OTA") {} void begin(); From 3b0870e2c13582e7a6a5e24cc3088fe2da0ebc0f Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 19 Dec 2025 11:37:04 +0100 Subject: [PATCH 232/409] variants: Heltec T114: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf --- variants/heltec_t114/T114Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 74e26455c6..b1fe1f533a 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -9,7 +9,7 @@ #define PIN_BAT_CTL 6 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class T114Board : public NRF52BoardOTA { +class T114Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: T114Board() : NRF52BoardOTA("T114_OTA") {} void begin(); From 041f67ab7106e3033d90513bfa0d2ba5c7f65417 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 19 Dec 2025 11:37:34 +0100 Subject: [PATCH 233/409] variants: Ikoka NRF: Use DC/DC regulator The Ikoka boards are based on the Xioa NRF52840 module which is known to have the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf --- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 2 +- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 2 +- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index acb58b3ce7..7912868a25 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -6,7 +6,7 @@ #ifdef IKOKA_NRF52 -class IkokaNrf52Board : public NRF52BoardOTA { +class IkokaNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: IkokaNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index c7e96921ec..3062de4966 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -6,7 +6,7 @@ #ifdef XIAO_NRF52 -class IkokaNanoNRFBoard : public NRF52BoardOTA { +class IkokaNanoNRFBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: IkokaNanoNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 00a26029c3..7edd85d1cc 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -6,7 +6,7 @@ #ifdef XIAO_NRF52 -class IkokaStickNRFBoard : public NRF52BoardOTA { +class IkokaStickNRFBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: IkokaStickNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); From bf93d6cf7a673b000981f66f650a5e8d05b03fa3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 19 Dec 2025 11:39:29 +0100 Subject: [PATCH 234/409] variants: Lilygo T-Echo (Lite): Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf --- variants/lilygo_techo/TechoBoard.h | 2 +- variants/lilygo_techo_lite/TechoBoard.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index c9bdc229be..660388276e 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -13,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52BoardOTA { +class TechoBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} void begin(); diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 8e6974bd35..6e816b4e63 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -13,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52BoardOTA { +class TechoBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} void begin(); From 465b481a2ed112836b9f818a11784763ba04d437 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 19 Dec 2025 11:40:16 +0100 Subject: [PATCH 235/409] variants: Mesh Pocket: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf --- variants/mesh_pocket/MeshPocket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index db65c99b18..8d22106f67 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -9,7 +9,7 @@ #define PIN_BAT_CTL 34 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class HeltecMeshPocket : public NRF52BoardOTA { +class HeltecMeshPocket : public NRF52BoardDCDC, public NRF52BoardOTA { public: HeltecMeshPocket() : NRF52BoardOTA("MESH_POCKET_OTA") {} void begin(); From 137eed3ede7eae919cb8b07cb5e8193d542c97d3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 19 Dec 2025 11:40:39 +0100 Subject: [PATCH 236/409] variants: Minewsemi ME25LS01: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf --- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 805f9dfa87..131148f864 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -20,7 +20,7 @@ #define PIN_VBAT_READ BATTERY_PIN #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class MinewsemiME25LS01Board : public NRF52BoardOTA { +class MinewsemiME25LS01Board : public NRF52BoardDCDC, public NRF52BoardOTA { protected: uint8_t btn_prev_state; From 80ca720002109c93ad629224515ce4d19d6f5bf7 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 19 Dec 2025 11:41:53 +0100 Subject: [PATCH 237/409] variants: ProMicro: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf --- variants/promicro/PromicroBoard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index c23ed1c99c..6719fc46a9 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -20,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 NRF52BoardOTA { +class PromicroBoard : public NRF52BoardDCDC, public NRF52BoardOTA { protected: uint8_t btn_prev_state; float adc_mult = ADC_MULTIPLIER; From 1651db81f9bf8c5ba93b1ce98da7ce369247b5c4 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 19 Dec 2025 11:42:34 +0100 Subject: [PATCH 238/409] variants: Sensecap Solar: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf --- variants/sensecap_solar/SenseCapSolarBoard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index dfe007bf7f..bb67f1e347 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -4,7 +4,7 @@ #include #include -class SenseCapSolarBoard : public NRF52BoardOTA { +class SenseCapSolarBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: SenseCapSolarBoard() : NRF52BoardOTA("SENSECAP_SOLAR_OTA") {} void begin(); From 686d887f723a1db57491ebe68e1277292433ff52 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Sat, 20 Dec 2025 10:39:57 +0100 Subject: [PATCH 239/409] variants: T1000E: Add OTA support To reduce the number of different code paths, add OTA support for the remaining NRF52 boards. Signed-off-by: Frieder Schrempf --- variants/t1000-e/T1000eBoard.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index f26bdf02ef..bc6e9c033e 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -4,11 +4,12 @@ #include #include -class T1000eBoard : public NRF52BoardDCDC { +class T1000eBoard : public NRF52BoardDCDC, public NRF52BoardOTA { protected: uint8_t btn_prev_state; public: + T1000eBoard() : NRF52BoardOTA("T1000E_OTA") {} void begin(); uint16_t getBattMilliVolts() override { @@ -89,6 +90,4 @@ class T1000eBoard : public NRF52BoardDCDC { sd_power_system_off(); } - -// bool startOTAUpdate(const char* id, char reply[]) override; }; From 4f46ec75dda7a4ed850f3dc42b426432d8256b33 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Sat, 20 Dec 2025 10:44:13 +0100 Subject: [PATCH 240/409] Remove NRF52BoardOTA class and integrate it into NRF52Board As all NRF52 boards now have OTA support, let's remove the subclass and integrate it into the base class. Signed-off-by: Frieder Schrempf --- src/helpers/NRF52Board.cpp | 2 +- src/helpers/NRF52Board.h | 13 ++++--------- 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 ++-- .../minewsemi_me25ls01/MinewsemiME25LS01Board.h | 4 ++-- variants/nano_g2_ultra/nano-g2.h | 4 ++-- 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 ++-- 22 files changed, 45 insertions(+), 50 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index c0d58314e2..8f60823c10 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -54,7 +54,7 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } -bool NRF52BoardOTA::startOTAUpdate(const char *id, char reply[]) { +bool NRF52Board::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() diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 0d6c0a4316..f464b79af0 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -8,12 +8,15 @@ class NRF52Board : public mesh::MainBoard { protected: uint8_t startup_reason; + char *ota_name; public: + NRF52Board(char *otaname) : ota_name(otaname) {} virtual void begin(); virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + virtual bool startOTAUpdate(const char *id, char reply[]) override; }; /* @@ -25,15 +28,7 @@ class NRF52Board : public mesh::MainBoard { */ class NRF52BoardDCDC : virtual public NRF52Board { public: + NRF52BoardDCDC() {} 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.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 92d69f3ae4..8163362559 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 NRF52BoardDCDC, public NRF52BoardOTA { +class MeshSolarBoard : public NRF52BoardDCDC { public: - MeshSolarBoard() : NRF52BoardOTA("MESH_SOLAR_OTA") {} + MeshSolarBoard() : NRF52Board("MESH_SOLAR_OTA") {} void begin(); uint16_t getBattMilliVolts() override { diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index b1fe1f533a..340cfa9713 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -9,9 +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 NRF52BoardDCDC, public NRF52BoardOTA { +class T114Board : public NRF52BoardDCDC { public: - T114Board() : NRF52BoardOTA("T114_OTA") {} + T114Board() : NRF52Board("T114_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 7912868a25..372dac5662 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -6,9 +6,9 @@ #ifdef IKOKA_NRF52 -class IkokaNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class IkokaNrf52Board : public NRF52BoardDCDC { public: - IkokaNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} + IkokaNrf52Board() : NRF52Board("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index 3062de4966..eb05092e02 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -6,9 +6,9 @@ #ifdef XIAO_NRF52 -class IkokaNanoNRFBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class IkokaNanoNRFBoard : public NRF52BoardDCDC { public: - IkokaNanoNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} + IkokaNanoNRFBoard() : NRF52Board("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 7edd85d1cc..3c04930f1e 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -6,9 +6,9 @@ #ifdef XIAO_NRF52 -class IkokaStickNRFBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class IkokaStickNRFBoard : public NRF52BoardDCDC { public: - IkokaStickNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} + IkokaStickNRFBoard() : NRF52Board("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index a9844c90b0..752b27e752 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -4,12 +4,12 @@ #include #include -class KeepteenLT1Board : public NRF52BoardOTA { +class KeepteenLT1Board : public NRF52Board { protected: uint8_t btn_prev_state; public: - KeepteenLT1Board() : NRF52BoardOTA("KeepteenLT1_OTA") {} + KeepteenLT1Board() : NRF52Board("KeepteenLT1_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 660388276e..e560cd14b4 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -13,9 +13,9 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class TechoBoard : public NRF52BoardDCDC { public: - TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} + TechoBoard() : NRF52Board("TECHO_OTA") {} void begin(); uint16_t getBattMilliVolts() override; diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 6e816b4e63..fda393e7f9 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -13,9 +13,9 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class TechoBoard : public NRF52BoardDCDC { public: - TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} + TechoBoard() : NRF52Board("TECHO_OTA") {} void begin(); uint16_t getBattMilliVolts() override; diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 8d22106f67..478bd56d71 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -9,9 +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 NRF52BoardDCDC, public NRF52BoardOTA { +class HeltecMeshPocket : public NRF52BoardDCDC { public: - HeltecMeshPocket() : NRF52BoardOTA("MESH_POCKET_OTA") {} + HeltecMeshPocket() : NRF52Board("MESH_POCKET_OTA") {} void begin(); uint16_t getBattMilliVolts() override { diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 131148f864..6858a1062f 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 NRF52BoardDCDC, public NRF52BoardOTA { +class MinewsemiME25LS01Board : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; public: - MinewsemiME25LS01Board() : NRF52BoardOTA("Minewsemi_OTA") {} + MinewsemiME25LS01Board() : NRF52Board("Minewsemi_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 4f7eb0741d..cf771efe7d 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -35,9 +35,9 @@ #define PIN_VBAT_READ (0 + 2) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class NanoG2Ultra : public NRF52BoardOTA { +class NanoG2Ultra : public NRF52Board { public: - NanoG2Ultra() : NRF52BoardOTA("NANO_G2_OTA") {} + NanoG2Ultra() : NRF52Board("NANO_G2_OTA") {} void begin(); uint16_t getBattMilliVolts() override; diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 6719fc46a9..7b6afb1b3d 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -20,13 +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 NRF52BoardDCDC, public NRF52BoardOTA { +class PromicroBoard : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; float adc_mult = ADC_MULTIPLIER; public: - PromicroBoard() : NRF52BoardOTA("ProMicro_OTA") {} + PromicroBoard() : NRF52Board("ProMicro_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index a181256b0d..a253e14268 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -29,9 +29,9 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class RAK4631Board : public NRF52BoardDCDC { public: - RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {} + RAK4631Board() : NRF52Board("RAK4631_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index 9aa457d248..cc5aa06f59 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -8,9 +8,9 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAKWismeshTagBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class RAKWismeshTagBoard : public NRF52BoardDCDC { public: - RAKWismeshTagBoard() : NRF52BoardOTA("WISMESHTAG_OTA") {} + RAKWismeshTagBoard() : NRF52Board("WISMESHTAG_OTA") {} void begin(); #if defined(P_LORA_TX_LED) && defined(LED_STATE_ON) diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index bb67f1e347..67215b8e09 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -4,9 +4,9 @@ #include #include -class SenseCapSolarBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class SenseCapSolarBoard : public NRF52BoardDCDC { public: - SenseCapSolarBoard() : NRF52BoardOTA("SENSECAP_SOLAR_OTA") {} + SenseCapSolarBoard() : NRF52Board("SENSECAP_SOLAR_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index bc6e9c033e..4922360779 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -4,12 +4,12 @@ #include #include -class T1000eBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class T1000eBoard : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; public: - T1000eBoard() : NRF52BoardOTA("T1000E_OTA") {} + T1000eBoard() : NRF52Board("T1000E_OTA") {} void begin(); uint16_t getBattMilliVolts() override { diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index 500a02658a..ebc46e6e6f 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -13,9 +13,9 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM1Board : public NRF52BoardOTA { +class ThinkNodeM1Board : public NRF52Board { public: - ThinkNodeM1Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {} + ThinkNodeM1Board() : NRF52Board("THINKNODE_M1_OTA") {} void begin(); uint16_t getBattMilliVolts() override; diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index bfd87d3963..052238e66f 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -4,12 +4,12 @@ #include #include -class WioTrackerL1Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class WioTrackerL1Board : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; public: - WioTrackerL1Board() : NRF52BoardOTA("WioTrackerL1 OTA") {} + WioTrackerL1Board() : NRF52Board("WioTrackerL1 OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index adcea87765..26f95c8684 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -11,9 +11,9 @@ #endif #define Serial Serial1 -class WioWM1110Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class WioWM1110Board : public NRF52BoardDCDC { public: - WioWM1110Board() : NRF52BoardOTA("WM1110_OTA") {} + WioWM1110Board() : NRF52Board("WM1110_OTA") {} void begin(); #if defined(LED_GREEN) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 1c46dfeee6..bf3115d3cb 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -6,9 +6,9 @@ #ifdef XIAO_NRF52 -class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class XiaoNrf52Board : public NRF52BoardDCDC { public: - XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} + XiaoNrf52Board() : NRF52Board("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) From 5475043083756b4bed07da6e6dec683f5d5f99a8 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 11:07:31 +1100 Subject: [PATCH 241/409] * new ANON_REQ_TYPE_VER_OWNER * CommonCLI: new "get/set owner.info ..." --- examples/simple_repeater/MyMesh.cpp | 27 +++++++++++++++++++++++++-- examples/simple_repeater/MyMesh.h | 3 ++- src/helpers/CommonCLI.cpp | 25 +++++++++++++++++++++++-- src/helpers/CommonCLI.h | 1 + 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 261184c577..eb561939ef 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -52,6 +52,7 @@ #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define ANON_REQ_TYPE_REGIONS 0x01 +#define ANON_REQ_TYPE_VER_OWNER 0x02 #define CLI_REPLY_DELAY_MILLIS 600 @@ -142,7 +143,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { - if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); @@ -159,6 +160,26 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } +uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + // other params?? + + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + + sprintf((char *) &reply_data[8], "%s,%s", FIRMWARE_VERSION, _prefs.owner_info); + + return 8 + strlen((char *) &reply_data[8]); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -474,6 +495,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { + reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } @@ -694,7 +717,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), discover_limiter(4, 120), // max 4 every 2 minutes - regions_limiter(4, 180) // max 4 every 3 minutes + anon_limiter(4, 180) // max 4 every 3 minutes #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 49952770db..f63aee2491 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -95,7 +95,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionMap region_map, temp_map; RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; - RateLimiter discover_limiter, regions_limiter; + RateLimiter discover_limiter, anon_limiter; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS @@ -117,6 +117,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 78e1b5e0bb..14b67b39b9 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -72,7 +72,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - // 170 + file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 + // 290 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -155,7 +156,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - // 170 + file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 + // 290 file.close(); } @@ -301,6 +303,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %d", (uint32_t)_prefs->flood_max); } else if (memcmp(config, "direct.txdelay", 14) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor)); + } else if (memcmp(config, "owner.info", 10) == 0) { + *reply++ = '>'; + *reply++ = ' '; + const char* sp = _prefs->owner_info; + while (*sp) { + *reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|' + sp++; + } + *reply = 0; // set null terminator } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { sprintf(reply, "> %d", (uint32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { @@ -479,6 +490,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { strcpy(reply, "Error, cannot be negative"); } + } else if (memcmp(config, "owner.info ", 11) == 0) { + config += 11; + char *dp = _prefs->owner_info; + while (*config && dp - _prefs->owner_info < sizeof(_prefs->owner_info)-1) { + *dp++ = (*config == '|') ? '\n' : *config; // translate '|' to newline chars + config++; + } + *dp = 0; + savePrefs(); + strcpy(reply, "OK"); } else if (memcmp(config, "tx ", 3) == 0) { _prefs->tx_power_dbm = atoi(&config[3]); savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 642a4cce3a..3b1d05f96a 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -50,6 +50,7 @@ struct NodePrefs { // persisted to file uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; float adc_multiplier; + char owner_info[120]; }; class CommonCLICallbacks { From 2a035ad8167f03901ff064d637b7ae714c57bcd2 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 13:20:20 +1100 Subject: [PATCH 242/409] * ANON_REQ_TYPE_VER_OWNER, now includes node_name --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index eb561939ef..d86e97bc99 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -173,7 +173,7 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) - sprintf((char *) &reply_data[8], "%s,%s", FIRMWARE_VERSION, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } From fd69acb4212fef2ad955860de4a9955e309ce4d2 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 13:54:18 +1100 Subject: [PATCH 243/409] * new ANON_REQ_TYPE_VER (for just simple clock + ver info) --- examples/simple_repeater/MyMesh.cpp | 29 ++++++++++++++++++++++------- examples/simple_repeater/MyMesh.h | 1 + 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d86e97bc99..38f81ff9fe 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -53,6 +53,7 @@ #define ANON_REQ_TYPE_REGIONS 0x01 #define ANON_REQ_TYPE_VER_OWNER 0x02 +#define ANON_REQ_TYPE_VER 0x03 #define CLI_REPLY_DELAY_MILLIS 600 @@ -148,12 +149,10 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - // other params?? memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) return 8 + region_map.exportNamesTo((char *) &reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); // reply length } @@ -166,13 +165,10 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - // other params?? memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) - + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length @@ -180,6 +176,23 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen return 0; } +uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) + strcpy((char *) &reply_data[8], FIRMWARE_VERSION); + + return 8 + strlen((char *) &reply_data[8]); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -497,6 +510,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_VER && packet->isRouteDirect()) { + reply_len = handleAnonVerReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f63aee2491..96a51da81c 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -118,6 +118,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 65796c8f2043dd2a3bc4327dfe091304ccbe6945 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 16:28:08 +1100 Subject: [PATCH 244/409] * CommonCLI: added "set name ..." validation * ANON_REQ_TYPE_VER_OWNER, now removes commas from node_name --- examples/simple_repeater/MyMesh.cpp | 13 ++++++++++++- src/helpers/CommonCLI.cpp | 18 +++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 38f81ff9fe..c50d860f11 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -159,6 +159,14 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } +static void sanitiseName(char* dest, const char* src) { + while (*src) { + *dest++ = (*src == ',') ? ' ' : *src; + src++; + } + *dest = 0; +} + uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} @@ -166,10 +174,13 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_path, data, reply_path_len); // data += reply_path_len; + char tmp[sizeof(_prefs.node_name)]; + sanitiseName(tmp, _prefs.node_name); + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, tmp, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 14b67b39b9..2fc93006b0 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -14,6 +14,14 @@ static uint32_t _atoi(const char* sp) { return n; } +static bool isValidName(const char *n) { + while (*n) { + if (*n == '[' || *n == ']' || *n == '/' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false; + n++; + } + return true; +} + void CommonCLI::loadPrefs(FILESYSTEM* fs) { if (fs->exists("/com_prefs")) { loadPrefsInt(fs, "/com_prefs"); // new filename @@ -421,9 +429,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "Error, invalid key"); } } else if (memcmp(config, "name ", 5) == 0) { - StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); - savePrefs(); - strcpy(reply, "OK"); + if (isValidName(&config[5])) { + StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, bad chars"); + } } else if (memcmp(config, "repeat ", 7) == 0) { _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; savePrefs(); From 4e4f6d92a0fced540cded8fd7866a4fe4e1b2557 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 16:32:08 +1100 Subject: [PATCH 245/409] * ANON_REQ_TYPE_VER_OWNER now delimited by newline chars --- examples/simple_repeater/MyMesh.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c50d860f11..d504fec9fb 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -159,14 +159,6 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } -static void sanitiseName(char* dest, const char* src) { - while (*src) { - *dest++ = (*src == ',') ? ' ' : *src; - src++; - } - *dest = 0; -} - uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} @@ -174,13 +166,10 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - char tmp[sizeof(_prefs.node_name)]; - sanitiseName(tmp, _prefs.node_name); - memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, tmp, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } From b6110eee38b2c7b80b36f54186095430e975bc83 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 12 Jan 2026 16:58:35 +1100 Subject: [PATCH 246/409] * new req/resp (after login): REQ_TYPE_GET_OWNER_INFO (includes firmware-ver) * ANON_REQ_TYPE_OWNER, firmware-ver removed (security exploit) * ANON_REQ_TYPE_BASIC, formware-ver removed, just remote clock + some 'feature' bits * CTL_TYPE_NODE_DISCOVER_REQ now ingored if 'repeat off' has been set --- examples/simple_repeater/MyMesh.cpp | 39 +++++++++++++++++++---------- examples/simple_repeater/MyMesh.h | 4 +-- src/helpers/RegionMap.cpp | 5 ++-- src/helpers/RegionMap.h | 2 +- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d504fec9fb..54f3ef7954 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -48,12 +48,13 @@ #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define REQ_TYPE_GET_ACCESS_LIST 0x05 #define REQ_TYPE_GET_NEIGHBOURS 0x06 +#define REQ_TYPE_GET_OWNER_INFO 0x07 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define ANON_REQ_TYPE_REGIONS 0x01 -#define ANON_REQ_TYPE_VER_OWNER 0x02 -#define ANON_REQ_TYPE_VER 0x03 +#define ANON_REQ_TYPE_OWNER 0x02 +#define ANON_REQ_TYPE_BASIC 0x03 // just remote clock #define CLI_REPLY_DELAY_MILLIS 600 @@ -159,7 +160,7 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } -uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; @@ -169,14 +170,14 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s\n%s", _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } return 0; } -uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; @@ -186,9 +187,16 @@ uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_t memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - strcpy((char *) &reply_data[8], FIRMWARE_VERSION); - - return 8 + strlen((char *) &reply_data[8]); // reply length + reply_data[8] = 0; // features +#ifdef WITH_RS232_BRIDGE + reply_data[8] |= 0x01; // is bridge, type UART +#elif WITH_ESPNOW_BRIDGE + reply_data[8] |= 0x03; // is bridge, type ESP-NOW +#endif + if (_prefs.disable_fwd) { // is this repeater currently disabled + reply_data[8] |= 0x80; // is disabled + } + return 9; // reply length } return 0; } @@ -350,6 +358,9 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return reply_offset; } + } else if (payload[0] == REQ_TYPE_GET_OWNER_INFO) { + sprintf((char *) &reply_data[4], "%s\n%s", FIRMWARE_VERSION, _prefs.owner_info); + return 4 + strlen((char *) &reply_data[4]); } return 0; // unknown command } @@ -508,10 +519,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); - } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { - reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); - } else if (data[4] == ANON_REQ_TYPE_VER && packet->isRouteDirect()) { - reply_len = handleAnonVerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_OWNER && packet->isRouteDirect()) { + reply_len = handleAnonOwnerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_BASIC && packet->isRouteDirect()) { + reply_len = handleAnonClockReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } @@ -700,7 +711,9 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t void MyMesh::onControlDataRecv(mesh::Packet* packet) { uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits - if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) { + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 + && !_prefs.disable_fwd && discover_limiter.allow(rtc_clock.getCurrentTime()) + ) { int i = 1; uint8_t filter = packet->payload[i++]; uint32_t tag; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 96a51da81c..f930ee7eb3 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -117,8 +117,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); - uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); - uint8_t handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index e227532af4..fbc5f01729 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -9,8 +9,9 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { strcpy(wildcard.name, "*"); } -bool RegionMap::is_name_char(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; +bool RegionMap::is_name_char(uint8_t c) { + // accept all alpha-num or accented characters, but exclude most punctuation chars + return c == '-' || c == '#' || (c >= '0' && c <= '9') || c >= 'A'; } static File openWrite(FILESYSTEM* _fs, const char* filename) { diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index dcd9a77452..01174d0949 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -30,7 +30,7 @@ class RegionMap { public: RegionMap(TransportKeyStore& store); - static bool is_name_char(char c); + static bool is_name_char(uint8_t c); bool load(FILESYSTEM* _fs, const char* path=NULL); bool save(FILESYSTEM* _fs, const char* path=NULL); From 69a71d0e25b956e70d33ada889ff0580d6dbb060 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 12 Jan 2026 17:47:51 +1100 Subject: [PATCH 247/409] * repeater login response, FIRMWARE_VER_LEVEL now bumped to 2 --- docs/payloads.md | 53 ++++++++++++++++++++++++++++- examples/simple_repeater/MyMesh.cpp | 7 ++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/docs/payloads.md b/docs/payloads.md index 5a41e69cef..4742bfbbc8 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -103,7 +103,9 @@ Request type | `0x02` | keepalive | (deprecated) | | `0x03` | get telemetry data | TODO | | `0x04` | get min,max,avg data | sensor nodes - get min, max, average for given time span | -| `0x05` | get access list | get node's approved access list | +| `0x05` | get access list | get node's approved access list | +| `0x06` | get neighbors | get repeater node's neighbors | +| `0x07` | get owner info | get repeater firmware-ver/name/owner info | ### Get stats @@ -132,6 +134,27 @@ Gets information about the node, possibly including the following: Request data about sensors on the node, including battery level. +### Get Telemetry + +TODO + +### Get Min/Max/Ave (Sensor nodes) + +TODO + +### Get Access List + +TODO + +### Get Neighors + +TODO + +### Get Owner Info + +TODO + + ## Response | Field | Size (bytes) | Description | @@ -179,6 +202,34 @@ txt_type | timestamp | 4 | sender time (unix timestamp) | | password | rest of message | password for repeater/sensor | +## Repeater - Regions request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x01 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + +## Repeater - Owner info request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x02 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + +## Repeater - Clock and status request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x03 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + + # Group text message / datagram | Field | Size (bytes) | Description | diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 54f3ef7954..d926148d62 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -41,14 +41,14 @@ #define TXT_ACK_DELAY 200 #endif -#define FIRMWARE_VER_LEVEL 1 +#define FIRMWARE_VER_LEVEL 2 #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define REQ_TYPE_GET_ACCESS_LIST 0x05 #define REQ_TYPE_GET_NEIGHBOURS 0x06 -#define REQ_TYPE_GET_OWNER_INFO 0x07 +#define REQ_TYPE_GET_OWNER_INFO 0x07 // FIRMWARE_VER_LEVEL >= 2 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ @@ -196,6 +196,7 @@ uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender if (_prefs.disable_fwd) { // is this repeater currently disabled reply_data[8] |= 0x80; // is disabled } + // TODO: add some kind of moving-window utilisation metric, so can query 'how busy' is this repeater return 9; // reply length } return 0; @@ -359,7 +360,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return reply_offset; } } else if (payload[0] == REQ_TYPE_GET_OWNER_INFO) { - sprintf((char *) &reply_data[4], "%s\n%s", FIRMWARE_VERSION, _prefs.owner_info); + sprintf((char *) &reply_data[4], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 4 + strlen((char *) &reply_data[4]); } return 0; // unknown command From 266e4893fda2b42e8e7e5f08239a827a13ef26dd Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Mon, 12 Jan 2026 19:19:23 +0100 Subject: [PATCH 248/409] remove serial debug logging from t3s3 sx1276 companion usb --- variants/lilygo_t3s3_sx1276/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index f544be113d..5a7ece2cbd 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -138,8 +138,6 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} + + From 324eab93948868dd4d5c86bb23117d243e58f3e3 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Mon, 12 Jan 2026 19:29:32 +0100 Subject: [PATCH 249/409] cleanup ikoka variants and add all supported sensors --- variants/ikoka_handheld_nrf/platformio.ini | 48 +++++----- variants/ikoka_nano_nrf/platformio.ini | 104 +++++++-------------- variants/ikoka_stick_nrf/platformio.ini | 84 ++++++----------- 3 files changed, 90 insertions(+), 146 deletions(-) diff --git a/variants/ikoka_handheld_nrf/platformio.ini b/variants/ikoka_handheld_nrf/platformio.ini index e4643c3831..d2bbeffe40 100644 --- a/variants/ikoka_handheld_nrf/platformio.ini +++ b/variants/ikoka_handheld_nrf/platformio.ini @@ -1,8 +1,5 @@ -[ikoka_nrf52] -extends = Xiao_nrf52 -lib_deps = ${nrf52_base.lib_deps} - ${sensor_base.lib_deps} - densaugeo/base64 @ ~1.4.0 +[ikoka_handheld_nrf] +extends = nrf52_base build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_7.3.0_API/include @@ -26,12 +23,15 @@ build_flags = ${nrf52_base.build_flags} build_src_filter = ${nrf52_base.build_src_filter} +<../variants/ikoka_handheld_nrf> + +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + densaugeo/base64 @ ~1.4.0 # larger screen has a different driver, this is for the 0.96 inch -[ikoka_nrf52_ssd1306_companion] -lib_deps = ${ikoka_nrf52.lib_deps} +[ikoka_handheld_nrf_ssd1306_companion] +lib_deps = ${ikoka_handheld_nrf.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 -build_flags = ${ikoka_nrf52.build_flags} +build_flags = ${ikoka_handheld_nrf.build_flags} -D DISPLAY_CLASS=SSD1306Display -D DISPLAY_ROTATION=0 -D PIN_WIRE_SCL=D6 @@ -42,62 +42,62 @@ build_flags = ${ikoka_nrf52.build_flags} -D OFFLINE_QUEUE_SIZE=256 -D QSPIFLASH=1 -I examples/companion_radio/ui-new -build_src_filter = ${ikoka_nrf52.build_src_filter} +build_src_filter = ${ikoka_handheld_nrf.build_src_filter} + +<../examples/companion_radio/ui-new/UITask.cpp> +<../examples/companion_radio/*.cpp> [env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_ble] extends = ikoka_nrf52 -build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} +build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D BLE_PIN_CODE=123456 -D LORA_TX_POWER=20 -build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} +build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} + [env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_ble] extends = ikoka_nrf52 -build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} +build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D BLE_PIN_CODE=123456 -D LORA_TX_POWER=20 - -D DISPLAY_ROTATION=2 -build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + -D DISPLAY_ROTATION=2 +build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} + [env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_usb] extends = ikoka_nrf52 -build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} +build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D LORA_TX_POWER=20 -build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} +build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_usb] extends = ikoka_nrf52 -build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} +build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D LORA_TX_POWER=20 -D DISPLAY_ROTATION=2 -build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} +build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_repeater] extends = ikoka_nrf52 build_flags = - ${ikoka_nrf52.build_flags} + ${ikoka_handheld_nrf.build_flags} -D ADVERT_NAME='"ikoka_handheld Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 - -D LORA_TX_POWER=20 -build_src_filter = ${ikoka_nrf52.build_src_filter} + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_handheld_nrf.build_src_filter} +<../examples/simple_repeater/*.cpp> [env:ikoka_handheld_nrf_e22_30dbm_room_server] extends = ikoka_nrf52 build_flags = - ${ikoka_nrf52.build_flags} + ${ikoka_handheld_nrf.build_flags} -D ADVERT_NAME='"ikoka_handheld Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D LORA_TX_POWER=20 -build_src_filter = ${ikoka_nrf52.build_src_filter} + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_handheld_nrf.build_src_filter} +<../examples/simple_room_server/*.cpp> diff --git a/variants/ikoka_nano_nrf/platformio.ini b/variants/ikoka_nano_nrf/platformio.ini index abfbcf6707..08b1101bf2 100644 --- a/variants/ikoka_nano_nrf/platformio.ini +++ b/variants/ikoka_nano_nrf/platformio.ini @@ -1,151 +1,124 @@ -[nrf52840_xiao] +[ikoka_nano_nrf] extends = nrf52_base -platform_packages = - toolchain-gccarmnoneeabi@~1.100301.0 - framework-arduinoadafruitnrf52 board = seeed-xiao-afruitnrf52-nrf52840 board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -D NRF52_PLATFORM -D XIAO_NRF52 -I lib/nrf52/s140_nrf52_7.3.0_API/include -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 -lib_ignore = - BluetoothOTA - lvgl - lib5b4 -lib_deps = - ${nrf52_base.lib_deps} - rweather/Crypto @ ^0.4.0 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit SSD1306 @ ^2.5.13 - -[ikoka_nano_nrf_baseboard] -extends = nrf52840_xiao -;board_build.ldscript = boards/nrf52840_s140_v7.ld -build_flags = ${nrf52840_xiao.build_flags} - -D P_LORA_TX_LED=11 -I variants/ikoka_nano_nrf -I src/helpers/nrf52 + -D P_LORA_TX_LED=11 -D DISPLAY_CLASS=NullDisplayDriver -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D P_LORA_DIO_1=D1 -; -D P_LORA_BUSY=D3 - -D P_LORA_BUSY=D2 ; specific to ikoka nano variant. -; -D P_LORA_RESET=D2 - -D P_LORA_RESET=D3 ; specific to ikoka nano variant. -; -D P_LORA_NSS=D4 - -D P_LORA_NSS=D0 ; specific to ikoka nano variant. -; -D SX126X_RXEN=D5 + -D P_LORA_BUSY=D2 + -D P_LORA_RESET=D3 + -D P_LORA_NSS=D0 -D SX126X_RXEN=D7 -D SX126X_TXEN=RADIOLIB_NC -D SX126X_DIO2_AS_RF_SWITCH=1 -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D PIN_WIRE_SCL=5 ; specific to ikoka nano variant. - -D PIN_WIRE_SDA=4 ; specific to ikoka nano variant. - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 + -D PIN_WIRE_SCL=5 + -D PIN_WIRE_SDA=4 + -UENV_INCLUDE_GPS debug_tool = jlink upload_protocol = nrfutil - - -;;; abstracted hardware variants +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} [ikoka_nano_nrf_e22_22dbm] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf ; No PA in this model, full 22dBm build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Nano-E22-22dBm (Xiao_nrf52)"' -D LORA_TX_POWER=22 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} + + + +<../variants/ikoka_nano_nrf> [ikoka_nano_nrf_e22_30dbm] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf ; limit txpower to 20dBm on E22-900M30S. Anything higher will ; cause distortion in the PA output. 20dBm in -> 30dBm out build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Nano-E22-30dBm (Xiao_nrf52)"' -D LORA_TX_POWER=20 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} + + + +<../variants/ikoka_nano_nrf> [ikoka_nano_nrf_e22_33dbm] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf ; limit txpower to 9dBm on E22-900M33S to avoid hardware damage ; to the rf amplifier frontend. 9dBm in -> 33dBm out build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Nano-E22-33dBm (Xiao_nrf52)"' -D LORA_TX_POWER=9 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} + + + +<../variants/ikoka_nano_nrf> -;;; abstracted firmware roles - [ikoka_nano_nrf_companion_radio_ble] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 -I examples/companion_radio/ui-new + -D QSPIFLASH=1 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${ikoka_nano_nrf_baseboard.lib_deps} + ${ikoka_nano_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 [ikoka_nano_nrf_companion_radio_usb] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -I examples/companion_radio/ui-new + -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${ikoka_nano_nrf_baseboard.lib_deps} + ${ikoka_nano_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 [ikoka_nano_nrf_repeater] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D ADVERT_NAME='"Ikoka Nano Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -153,26 +126,23 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<../examples/simple_repeater/*.cpp> [ikoka_nano_nrf_room_server] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D ADVERT_NAME='"Ikoka Nano Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<../examples/simple_room_server/*.cpp> -;;; hardware + firmware variants - ;;; 22dBm EBYTE E22-900M22 variants - [env:ikoka_nano_nrf_22dbm_companion_radio_usb] extends = ikoka_nano_nrf_e22_22dbm @@ -219,7 +189,6 @@ build_src_filter = ;;; 30dBm EBYTE E22-900M30 variants - [env:ikoka_nano_nrf_30dbm_companion_radio_usb] extends = ikoka_nano_nrf_e22_30dbm @@ -266,7 +235,6 @@ build_src_filter = ;;; 33dBm EBYTE E22-900M33 variants - [env:ikoka_nano_nrf_33dbm_companion_radio_usb] extends = ikoka_nano_nrf_e22_33dbm diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 9ced2bbb5c..4f66405436 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -1,34 +1,15 @@ -[nrf52840_xiao] +[ikoka_stick_nrf] extends = nrf52_base -platform_packages = - toolchain-gccarmnoneeabi@~1.100301.0 - framework-arduinoadafruitnrf52 board = seeed-xiao-afruitnrf52-nrf52840 board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -D NRF52_PLATFORM -D XIAO_NRF52 -I lib/nrf52/s140_nrf52_7.3.0_API/include -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 -lib_ignore = - BluetoothOTA - lvgl - lib5b4 -lib_deps = - ${nrf52_base.lib_deps} - rweather/Crypto @ ^0.4.0 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit SSD1306 @ ^2.5.13 - -[ikoka_stick_nrf_baseboard] -extends = nrf52840_xiao -;board_build.ldscript = boards/nrf52840_s140_v7.ld -build_flags = ${nrf52840_xiao.build_flags} - -D P_LORA_TX_LED=11 -I variants/ikoka_stick_nrf -I src/helpers/nrf52 + -D P_LORA_TX_LED=11 -D DISPLAY_CLASS=SSD1306Display -D DISPLAY_ROTATION=2 -D RADIO_CLASS=CustomSX1262 @@ -46,24 +27,17 @@ build_flags = ${nrf52840_xiao.build_flags} -D PIN_USER_BTN=0 -D PIN_WIRE_SCL=7 -D PIN_WIRE_SDA=6 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 -debug_tool = jlink -upload_protocol = nrfutil - - -;;; abstracted hardware variants +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} [ikoka_stick_nrf_e22_22dbm] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf ; No PA in this model, full 22dBm build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Stick-E22-22dBm (Xiao_nrf52)"' -D LORA_TX_POWER=22 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + @@ -71,14 +45,14 @@ build_src_filter = ${nrf52840_xiao.build_src_filter} +<../variants/ikoka_stick_nrf> [ikoka_stick_nrf_e22_30dbm] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf ; limit txpower to 20dBm on E22-900M30S. Anything higher will ; cause distortion in the PA output. 20dBm in -> 30dBm out build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Stick-E22-30dBm (Xiao_nrf52)"' -D LORA_TX_POWER=20 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + @@ -86,14 +60,14 @@ build_src_filter = ${nrf52840_xiao.build_src_filter} +<../variants/ikoka_stick_nrf> [ikoka_stick_nrf_e22_33dbm] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf ; limit txpower to 9dBm on E22-900M33S to avoid hardware damage ; to the rf amplifier frontend. 9dBm in -> 33dBm out build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Stick-E22-33dBm (Xiao_nrf52)"' -D LORA_TX_POWER=9 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + @@ -103,50 +77,52 @@ build_src_filter = ${nrf52840_xiao.build_src_filter} ;;; abstracted firmware roles [ikoka_stick_nrf_companion_radio_ble] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 -I examples/companion_radio/ui-new + -D QSPIFLASH=1 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${ikoka_stick_nrf_baseboard.lib_deps} + ${ikoka_stick_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 [ikoka_stick_nrf_companion_radio_usb] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -I examples/companion_radio/ui-new + -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${ikoka_stick_nrf_baseboard.lib_deps} + ${ikoka_stick_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 [ikoka_stick_nrf_repeater] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D ADVERT_NAME='"Ikoka Stick Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -154,21 +130,21 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + +<../examples/simple_repeater/*.cpp> [ikoka_stick_nrf_room_server] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D ADVERT_NAME='"Ikoka Stick Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} +<../examples/simple_room_server/*.cpp> ;;; hardware + firmware variants From a48b18518932791494d9838d277f0649491c4c1d Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Sun, 11 Jan 2026 22:03:48 +0100 Subject: [PATCH 250/409] DISABLE_DEBUG=1 env variable to build.sh --- build.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/build.sh b/build.sh index f212794173..b7f95dd742 100755 --- a/build.sh +++ b/build.sh @@ -29,6 +29,20 @@ $ sh build.sh build-repeater-firmwares Build all chat room server firmwares $ sh build.sh build-room-server-firmwares + +Environment Variables: + DISABLE_DEBUG=1: Disables all debug logging flags (MESH_DEBUG, MESH_PACKET_LOGGING, etc.) + If not set, debug flags from variant platformio.ini files are used. + +Examples: +Build without debug logging: +$ export FIRMWARE_VERSION=v1.0.0 +$ export DISABLE_DEBUG=1 +$ sh build.sh build-firmware RAK_4631_repeater + +Build with debug logging (default, uses flags from variant files): +$ export FIRMWARE_VERSION=v1.0.0 +$ sh build.sh build-firmware RAK_4631_repeater EOF } @@ -68,6 +82,13 @@ get_pio_envs_ending_with_string() { done } +# disable all debug logging flags if DISABLE_DEBUG=1 is set +disable_debug_flags() { + if [ "$DISABLE_DEBUG" == "1" ]; then + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG -UBLE_DEBUG_LOGGING -UWIFI_DEBUG_LOGGING -UBRIDGE_DEBUG -UGPS_NMEA_DEBUG -UCORE_DEBUG_LEVEL -UESPNOW_DEBUG_LOGGING -UDEBUG_RP2040_WIRE -UDEBUG_RP2040_SPI -UDEBUG_RP2040_CORE -UDEBUG_RP2040_PORT -URADIOLIB_DEBUG_SPI -UCFG_DEBUG -URADIOLIB_DEBUG_BASIC -URADIOLIB_DEBUG_PROTOCOL" + fi +} + # build firmware for the provided pio env in $1 build_firmware() { @@ -94,6 +115,9 @@ build_firmware() { # add firmware version info to end of existing platformio build flags in environment vars export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'" + # disable debug flags if requested + disable_debug_flags + # build firmware target pio run -e $1 From 06c4ca19ab064e359b6708c565be8c3e9bbc532a Mon Sep 17 00:00:00 2001 From: chrisdavis2110 Date: Tue, 13 Jan 2026 10:06:50 -0800 Subject: [PATCH 251/409] added variant rak3401 --- variants/rak3401/RAK3401Board.cpp | 37 ++++++ variants/rak3401/RAK3401Board.h | 81 ++++++++++++ variants/rak3401/platformio.ini | 135 +++++++++++++++++++ variants/rak3401/target.cpp | 66 ++++++++++ variants/rak3401/target.h | 30 +++++ variants/rak3401/variant.cpp | 43 +++++++ variants/rak3401/variant.h | 207 ++++++++++++++++++++++++++++++ 7 files changed, 599 insertions(+) create mode 100644 variants/rak3401/RAK3401Board.cpp create mode 100644 variants/rak3401/RAK3401Board.h create mode 100644 variants/rak3401/platformio.ini create mode 100644 variants/rak3401/target.cpp create mode 100644 variants/rak3401/target.h create mode 100644 variants/rak3401/variant.cpp create mode 100644 variants/rak3401/variant.h diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp new file mode 100644 index 0000000000..50499dffca --- /dev/null +++ b/variants/rak3401/RAK3401Board.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include "RAK3401Board.h" + +void RAK3401Board::begin() { + NRF52BoardDCDC::begin(); + pinMode(PIN_VBAT_READ, INPUT); +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#ifdef PIN_USER_BTN_ANA + pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP); +#endif + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); +#endif + + Wire.begin(); + + // Enable 3.3V periphery power rail (GPS, IO Module, etc.) + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up + +#ifdef P_LORA_PA_EN + // Initialize RAK13302 1W LoRa transceiver module PA control pin + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN, LOW); // Start with PA disabled + delay(10); // Allow PA module to initialize +#endif +} diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h new file mode 100644 index 0000000000..5188f274aa --- /dev/null +++ b/variants/rak3401/RAK3401Board.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include + +// LoRa radio module pins for RAK3401 with RAK13302 (uses SPI1) +#define P_LORA_DIO_1 10 +#define P_LORA_NSS 26 +#define P_LORA_RESET 4 +#define P_LORA_BUSY 9 +#define P_LORA_SCLK 3 // SPI1_SCK +#define P_LORA_MISO 29 // SPI1_MISO +#define P_LORA_MOSI 30 // SPI1_MOSI +#define SX126X_POWER_EN 21 + +//#define PIN_GPS_SDA 13 //GPS SDA pin (output option) +//#define PIN_GPS_SCL 14 //GPS SCL pin (output option) +//#define PIN_GPS_TX 16 //GPS TX pin +//#define PIN_GPS_RX 15 //GPS RX pin +#define PIN_GPS_1PPS 17 //GPS PPS pin +#define GPS_BAUD_RATE 9600 +#define GPS_ADDRESS 0x42 //i2c address for GPS + +// RAK13302 1W LoRa transceiver module PA control (WisBlock IO slot) +// The RAK13302 mounts to the IO slot and has an ANT_SW (antenna switch) pin that controls the PA +// This pin must be controlled during transmission to enable the 1W power amplifier +// +// According to RAK13302 datasheet: ANT_SW connects to IO3 on the IO slot +// RAK19007 base board pin mapping: IO3 = pin 31 (also available as AIN1/A1 for analog input) +// +// Default: Pin 31 (IO3) - ANT_SW pin from RAK13302 datasheet +// Override by defining P_LORA_PA_EN in platformio.ini if needed +#ifndef P_LORA_PA_EN + #define P_LORA_PA_EN 31 // ANT_SW pin from RAK13302 datasheet (IO3, pin 31 on RAK19007) +#endif + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// built-ins +#define PIN_VBAT_READ 5 +#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) + +// 3.3V periphery enable (GPS, IO Module, etc.) +#define PIN_3V3_EN (34) +#define WB_IO2 PIN_3V3_EN + +class RAK3401Board : public NRF52BoardDCDC, public NRF52BoardOTA { +public: + RAK3401Board() : NRF52BoardOTA("RAK3401_OTA") {} + void begin(); + + #define BATTERY_SAMPLES 8 + + uint16_t getBattMilliVolts() override { + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + return (ADC_MULTIPLIER * raw) / 4096; + } + + const char* getManufacturerName() const override { + return "RAK 3401"; + } + +#ifdef P_LORA_PA_EN + void onBeforeTransmit() override { + digitalWrite(P_LORA_PA_EN, HIGH); // Enable PA before transmission + } + + void onAfterTransmit() override { + digitalWrite(P_LORA_PA_EN, LOW); // Disable PA after transmission to save power + } +#endif +}; diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini new file mode 100644 index 0000000000..f1c4bd2df1 --- /dev/null +++ b/variants/rak3401/platformio.ini @@ -0,0 +1,135 @@ +[rak3401] +extends = nrf52_base +board = rak4631 +board_check = true +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I variants/rak3401 + -D RAK_4631 + -D RAK_3401 + -D RAK13302 + -D RAK_BOARD + -D PIN_BOARD_SCL=14 + -D PIN_BOARD_SDA=13 + -D PIN_GPS_TX=PIN_SERIAL1_RX + -D PIN_GPS_RX=PIN_SERIAL1_TX + -D PIN_GPS_EN=-1 + -D PIN_OLED_RESET=-1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/rak3401> + + + + + + +lib_deps = + ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 + +[env:RAK_3401_repeater] +extends = rak3401 +build_flags = + ${rak3401.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK3401 1W 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 = ${rak3401.build_src_filter} + + + +<../examples/simple_repeater> + +[env:RAK_3401_room_server] +extends = rak3401 +build_flags = + ${rak3401.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Test Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + + + +<../examples/simple_room_server> + +[env:RAK_3401_companion_radio_usb] +extends = rak3401 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${rak3401.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3401.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK_3401_companion_radio_ble] +extends = rak3401 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${rak3401.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3401.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK_3401_terminal_chat] +extends = rak3401 +build_flags = + ${rak3401.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${rak3401.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK_3401_sensor] +extends = rak3401 +build_flags = + ${rak3401.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK3401 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + + + +<../examples/simple_sensor> \ No newline at end of file diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp new file mode 100644 index 0000000000..ba827ad182 --- /dev/null +++ b/variants/rak3401/target.cpp @@ -0,0 +1,66 @@ +#include +#include "target.h" +#include + +RAK3401Board board; + +#ifndef PIN_USER_BTN + #define PIN_USER_BTN (-1) +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); + + #if defined(PIN_USER_BTN_ANA) + MomentaryButton analog_btn(PIN_USER_BTN_ANA, 1000, 20); + #endif +#endif + +// RAK3401 uses SPI1 for the RAK13302 LoRa module +// Note: nRF52 doesn't have a separate SPI1 object, so we use SPI but configure it with SPI1 pins +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + + // Configure SPI with SPI1 pins for RAK13302 + // nRF52 uses the same SPI peripheral but with different pin assignments + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/rak3401/target.h b/variants/rak3401/target.h new file mode 100644 index 0000000000..32f17cd1da --- /dev/null +++ b/variants/rak3401/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + extern DISPLAY_CLASS display; + #include + extern MomentaryButton user_btn; + #if defined(PIN_USER_BTN_ANA) + extern MomentaryButton analog_btn; + #endif +#endif + +extern RAK3401Board 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/rak3401/variant.cpp b/variants/rak3401/variant.cpp new file mode 100644 index 0000000000..db55920c20 --- /dev/null +++ b/variants/rak3401/variant.cpp @@ -0,0 +1,43 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h new file mode 100644 index 0000000000..03e9c2a8ab --- /dev/null +++ b/variants/rak3401/variant.h @@ -0,0 +1,207 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_RAK3401_ +#define _VARIANT_RAK3401_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Analog pins + */ +#define PIN_A0 (5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define WB_I2C1_SDA (13) // SENSOR_SLOT IO_SLOT +#define WB_I2C1_SCL (14) // SENSOR_SLOT IO_SLOT + +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define WB_IO5 PIN_NFC1 +#define WB_IO4 (4) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +// SPI1 pins for RAK13302 1W LoRa module +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (WB_I2C1_SDA) +#define PIN_WIRE_SCL (WB_I2C1_SCL) + +// QSPI Pins +#define PIN_QSPI_SCK 3 +#define PIN_QSPI_CS 26 +#define PIN_QSPI_IO0 30 +#define PIN_QSPI_IO1 29 +#define PIN_QSPI_IO2 28 +#define PIN_QSPI_IO3 2 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES IS25LP080D +#define EXTERNAL_FLASH_USE_QSPI + +// RAK13302 1W LoRa transceiver module (uses SPI1) +// LoRa radio module pins for RAK3401 with RAK13302 +#define P_LORA_DIO_1 10 +#define P_LORA_NSS 26 +#define P_LORA_RESET 4 +#define P_LORA_BUSY 9 +#define P_LORA_SCLK PIN_SPI1_SCK // 3 +#define P_LORA_MISO PIN_SPI1_MISO // 29 +#define P_LORA_MOSI PIN_SPI1_MOSI // 30 +#define SX126X_POWER_EN 21 + +// RAK13302 1W LoRa transceiver module PA control (WisBlock IO slot) +// The RAK13302 mounts to the IO slot and has an ANT_SW (antenna switch) pin that controls the PA +// This pin must be controlled during transmission to enable the 1W power amplifier +// +// According to RAK13302 datasheet: ANT_SW connects to IO3 on the IO slot +// RAK19007 base board pin mapping: IO3 = pin 31 (also available as AIN1/A1 for analog input) +// +// Default: Pin 31 (IO3) - ANT_SW pin from RAK13302 datasheet +// Override by defining P_LORA_PA_EN in platformio.ini if needed +#ifndef P_LORA_PA_EN + #define P_LORA_PA_EN 31 // ANT_SW pin from RAK13302 datasheet (IO3, pin 31 on RAK19007) +#endif + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings +#define PIN_3V3_EN (34) +#define WB_IO2 PIN_3V3_EN + +// RAK1910 GPS module +// If using the wisblock GPS module and pluged into Port A on WisBlock base +// IO1 is hooked to PPS (pin 12 on header) = gpio 17 +// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). +// Therefore must be 1 to keep peripherals powered +// Power is on the controllable 3V3_S rail +#define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// Battery +// The battery sense is hooked to pin A0 (5) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 + +#define HAS_RTC 1 + +#define RAK_4631 1 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From 4575800e4069004cf8fb024517b2b33387bd2e5a Mon Sep 17 00:00:00 2001 From: Socalix <48040807+Socalix@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:52:15 -0600 Subject: [PATCH 252/409] Turn on register 0x8B5 LSB for improved RX, turn off boosted gain --- src/helpers/radiolib/CustomSX1262.h | 8 ++++++++ variants/heltec_v4/platformio.ini | 11 ++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/helpers/radiolib/CustomSX1262.h b/src/helpers/radiolib/CustomSX1262.h index bfaea7c76a..be6812c6f3 100644 --- a/src/helpers/radiolib/CustomSX1262.h +++ b/src/helpers/radiolib/CustomSX1262.h @@ -76,6 +76,14 @@ class CustomSX1262 : public SX1262 { setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); #endif + // for improved RX with Heltec v4 + #ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + writeRegister(0x8B5, &r_data, 1); + #endif + return true; // success } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ecfd7889b1..9ab3e1625a 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -17,9 +17,9 @@ build_flags = -D P_LORA_SCLK=9 -D P_LORA_MISO=11 -D P_LORA_MOSI=10 - -D P_LORA_PA_POWER=7 ;power en - -D P_LORA_PA_EN=2 - -D P_LORA_PA_TX_EN=46 ;enable tx + -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - Power on GC1109 + -D P_LORA_PA_EN=2 ; PA CSD - Enable GC1109 + -D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low) -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 @@ -27,10 +27,11 @@ build_flags = -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output - -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX + -D SX126X_DIO2_AS_RF_SWITCH=true ; GC1109 CTX is controlled by SX1262 DIO2 -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 - -D SX126X_RX_BOOSTED_GAIN=1 +; -D SX126X_RX_BOOSTED_GAIN=1 ; Turned off for improved RX -D PIN_GPS_RX=38 -D PIN_GPS_TX=39 -D PIN_GPS_RESET=42 From 31f98bdd43c2a404ceea304a2d8dbfe2618cf498 Mon Sep 17 00:00:00 2001 From: Dustin Brewer Date: Wed, 14 Jan 2026 17:53:42 -0800 Subject: [PATCH 253/409] Fix Ikoka Stick builds --- variants/ikoka_stick_nrf/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 4f66405436..2e43b7004d 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -27,6 +27,7 @@ build_flags = ${nrf52_base.build_flags} -D PIN_USER_BTN=0 -D PIN_WIRE_SCL=7 -D PIN_WIRE_SDA=6 + -UENV_INCLUDE_GPS lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} From 403ce1db08249b51dc637bb1545d45894f1a03c7 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 13 Jan 2026 00:38:20 +1100 Subject: [PATCH 254/409] contacts: granular autoadd and overwrite-oldest --- examples/companion_radio/DataStore.cpp | 2 + examples/companion_radio/MyMesh.cpp | 70 +++++++++++++++++++++++++- examples/companion_radio/MyMesh.h | 4 ++ examples/companion_radio/NodePrefs.h | 1 + src/helpers/BaseChatMesh.cpp | 69 +++++++++++++++++++------ src/helpers/BaseChatMesh.h | 5 ++ 6 files changed, 134 insertions(+), 17 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 4faac97549..f61f53aeed 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -227,6 +227,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 + file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 file.close(); } @@ -261,6 +262,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 + file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 59a0078f44..803e3b035e 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -54,6 +54,8 @@ #define CMD_SEND_CONTROL_DATA 55 // v8+ #define CMD_GET_STATS 56 // v8+, second byte is stats type #define CMD_SEND_ANON_REQ 57 +#define CMD_SET_AUTOADD_CONFIG 58 +#define CMD_GET_AUTOADD_CONFIG 59 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -85,6 +87,7 @@ #define RESP_CODE_ADVERT_PATH 22 #define RESP_CODE_TUNING_PARAMS 23 #define RESP_CODE_STATS 24 // v8+, second byte is stats type +#define RESP_CODE_AUTOADD_CONFIG 25 #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -110,6 +113,8 @@ #define PUSH_CODE_BINARY_RESPONSE 0x8C #define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D #define PUSH_CODE_CONTROL_DATA 0x8E // v8+ +#define PUSH_CODE_CONTACT_DELETED 0x8F // used to notify client app of deleted contact when overwriting oldest +#define PUSH_CODE_CONTACTS_FULL 0x90 // used to notify client app that contacts storage is full #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -120,6 +125,15 @@ #define MAX_SIGN_DATA_LEN (8 * 1024) // 8K +// Auto-add config bitmask +// Bit 0: If set, overwrite oldest non-favourite contact when contacts file is full +// Bits 1-4: these indicate which contact types to auto-add when manual_contact_mode = 0x01 +#define AUTO_ADD_OVERWRITE_OLDEST (1 << 0) // 0x01 - overwrite oldest non-favourite when full +#define AUTO_ADD_CHAT (1 << 1) // 0x02 - auto-add Chat (Companion) (ADV_TYPE_CHAT) +#define AUTO_ADD_REPEATER (1 << 2) // 0x04 - auto-add Repeater (ADV_TYPE_REPEATER) +#define AUTO_ADD_ROOM_SERVER (1 << 3) // 0x08 - auto-add Room Server (ADV_TYPE_ROOM) +#define AUTO_ADD_SENSOR (1 << 4) // 0x10 - auto-add Sensor (ADV_TYPE_SENSOR) + void MyMesh::writeOKFrame() { uint8_t buf[1]; buf[0] = RESP_CODE_OK; @@ -262,9 +276,54 @@ bool MyMesh::isAutoAddEnabled() const { return (_prefs.manual_add_contacts & 1) == 0; } +bool MyMesh::shouldAutoAddContactType(uint8_t contact_type) const { + if ((_prefs.manual_add_contacts & 1) == 0) { + return true; + } + + uint8_t type_bit = 0; + switch (contact_type) { + case ADV_TYPE_CHAT: + type_bit = AUTO_ADD_CHAT; + break; + case ADV_TYPE_REPEATER: + type_bit = AUTO_ADD_REPEATER; + break; + case ADV_TYPE_ROOM: + type_bit = AUTO_ADD_ROOM_SERVER; + break; + case ADV_TYPE_SENSOR: + type_bit = AUTO_ADD_SENSOR; + break; + default: + return false; // Unknown type, don't auto-add + } + + return (_prefs.autoadd_config & type_bit) != 0; +} + +bool MyMesh::shouldOverwriteWhenFull() const { + return (_prefs.autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) != 0; +} + +void MyMesh::onContactOverwrite(const uint8_t* pub_key) { + if (_serial->isConnected()) { + out_frame[0] = PUSH_CODE_CONTACT_DELETED; + memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE); + _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); + } +} + +void MyMesh::onContactsFull() { + if (_serial->isConnected()) { + out_frame[0] = PUSH_CODE_CONTACTS_FULL; + _serial->writeFrame(out_frame, 1); + } +} + void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) { if (_serial->isConnected()) { - if (!isAutoAddEnabled() && is_new) { + if (!shouldAutoAddContactType(contact.type) && is_new) { writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact); } else { out_frame[0] = PUSH_CODE_ADVERT; @@ -1663,6 +1722,15 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_TABLE_FULL); } + } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { + _prefs.autoadd_config = cmd_frame[1]; + savePrefs(); + writeOKFrame(); + } else if (cmd_frame[0] == CMD_GET_AUTOADD_CONFIG) { + int i = 0; + out_frame[i++] = RESP_CODE_AUTOADD_CONFIG; + out_frame[i++] = _prefs.autoadd_config; + _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 1fcc5697df..a2b0033f0a 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -114,6 +114,10 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; bool isAutoAddEnabled() const override; + bool shouldAutoAddContactType(uint8_t type) const override; + bool shouldOverwriteWhenFull() const override; + void onContactsFull() override; + void onContactOverwrite(const uint8_t* pub_key) override; bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override; void onContactPathUpdated(const ContactInfo &contact) override; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index e9db5444fe..62cd416422 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -27,4 +27,5 @@ struct NodePrefs { // persisted to file uint8_t buzzer_quiet; uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) uint32_t gps_interval; // GPS read interval in seconds + uint8_t autoadd_config; // bitmask for auto-add contacts config }; \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 0818562895..241c5d329a 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -55,6 +55,28 @@ void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { } } +ContactInfo* BaseChatMesh::allocateContactSlot() { + if (num_contacts < MAX_CONTACTS) { + return &contacts[num_contacts++]; + } else if (shouldOverwriteWhenFull()) { + // Find oldest non-favourite contact by last_advert_timestamp + int oldest_idx = -1; + uint32_t oldest_timestamp = 0xFFFFFFFF; + for (int i = 0; i < num_contacts; i++) { + bool is_favourite = (contacts[i].flags & 0x01) != 0; + if (!is_favourite && contacts[i].last_advert_timestamp < oldest_timestamp) { + oldest_timestamp = contacts[i].last_advert_timestamp; + oldest_idx = i; + } + } + if (oldest_idx >= 0) { + onContactOverwrite(contacts[oldest_idx].id.pub_key); + return &contacts[oldest_idx]; + } + } + return NULL; // no space, no overwrite or all contacts are all favourites +} + void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { AdvertDataParser parser(app_data, app_data_len); if (!(parser.isValid() && parser.hasName())) { @@ -87,7 +109,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, bool is_new = false; if (from == NULL) { - if (!isAutoAddEnabled()) { + if (!shouldAutoAddContactType(parser.getType())) { ContactInfo ci; memset(&ci, 0, sizeof(ci)); ci.id = id; @@ -105,20 +127,33 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, } is_new = true; - if (num_contacts < MAX_CONTACTS) { - from = &contacts[num_contacts++]; - from->id = id; - from->out_path_len = -1; // initially out_path is unknown - from->gps_lat = 0; // initially unknown GPS loc - from->gps_lon = 0; - from->sync_since = 0; - - from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand - } else { - MESH_DEBUG_PRINTLN("onAdvertRecv: contacts table is full!"); + from = allocateContactSlot(); + if (from == NULL) { + ContactInfo ci; + memset(&ci, 0, sizeof(ci)); + ci.id = id; + ci.out_path_len = -1; // initially out_path is unknown + StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); + ci.type = parser.getType(); + if (parser.hasLatLon()) { + ci.gps_lat = parser.getIntLat(); + ci.gps_lon = parser.getIntLon(); + } + ci.last_advert_timestamp = timestamp; + ci.lastmod = getRTCClock()->getCurrentTime(); + onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know + onContactsFull(); + MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact"); return; } - } + + from->id = id; + from->out_path_len = -1; // initially out_path is unknown + from->gps_lat = 0; // initially unknown GPS loc + from->gps_lon = 0; + from->sync_since = 0; + from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand + } // update StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); @@ -722,10 +757,12 @@ ContactInfo* BaseChatMesh::lookupContactByPubKey(const uint8_t* pub_key, int pre } bool BaseChatMesh::addContact(const ContactInfo& contact) { - if (num_contacts < MAX_CONTACTS) { - auto dest = &contacts[num_contacts++]; + ContactInfo* dest = allocateContactSlot(); + if (dest) { *dest = contact; - + if (dest->last_advert_timestamp == 0) { // ensure non-zero timestamp to prevent contacts added from discover list being considered 'oldest' + dest->last_advert_timestamp = getRTCClock()->getCurrentTimeUnique(); + } dest->shared_secret_valid = false; // mark shared_secret as needing calculation return true; // success } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 40818fed6e..bfbf254efb 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -89,9 +89,14 @@ class BaseChatMesh : public mesh::Mesh { } void resetContacts() { num_contacts = 0; } + ContactInfo* allocateContactSlot(); // helper to find slot for new contact // 'UI' concepts, for sub-classes to implement virtual bool isAutoAddEnabled() const { return true; } + virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } + virtual void onContactsFull() {}; + virtual bool shouldOverwriteWhenFull() const { return false; } + virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; virtual void onContactPathUpdated(const ContactInfo& contact) = 0; From 741564dd48d29ea328b1fd041bf4891ee28aa6e7 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 13 Jan 2026 01:05:42 +1100 Subject: [PATCH 255/409] refactor: add populateContactFromAdvert() --- src/helpers/BaseChatMesh.cpp | 66 +++++++++++++++--------------------- src/helpers/BaseChatMesh.h | 1 + 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 241c5d329a..b68f48056e 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -77,6 +77,20 @@ ContactInfo* BaseChatMesh::allocateContactSlot() { return NULL; // no space, no overwrite or all contacts are all favourites } +void BaseChatMesh::populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp) { + memset(&ci, 0, sizeof(ci)); + ci.id = id; + ci.out_path_len = -1; // initially out_path is unknown + StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); + ci.type = parser.getType(); + if (parser.hasLatLon()) { + ci.gps_lat = parser.getIntLat(); + ci.gps_lon = parser.getIntLon(); + } + ci.last_advert_timestamp = timestamp; + ci.lastmod = getRTCClock()->getCurrentTime(); +} + void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { AdvertDataParser parser(app_data, app_data_len); if (!(parser.isValid() && parser.hasName())) { @@ -111,17 +125,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, if (from == NULL) { if (!shouldAutoAddContactType(parser.getType())) { ContactInfo ci; - memset(&ci, 0, sizeof(ci)); - ci.id = id; - ci.out_path_len = -1; // initially out_path is unknown - StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); - ci.type = parser.getType(); - if (parser.hasLatLon()) { - ci.gps_lat = parser.getIntLat(); - ci.gps_lon = parser.getIntLon(); - } - ci.last_advert_timestamp = timestamp; - ci.lastmod = getRTCClock()->getCurrentTime(); + populateContactFromAdvert(ci, id, parser, timestamp); onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know return; } @@ -130,40 +134,26 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, from = allocateContactSlot(); if (from == NULL) { ContactInfo ci; - memset(&ci, 0, sizeof(ci)); - ci.id = id; - ci.out_path_len = -1; // initially out_path is unknown - StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); - ci.type = parser.getType(); - if (parser.hasLatLon()) { - ci.gps_lat = parser.getIntLat(); - ci.gps_lon = parser.getIntLon(); - } - ci.last_advert_timestamp = timestamp; - ci.lastmod = getRTCClock()->getCurrentTime(); - onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know + populateContactFromAdvert(ci, id, parser, timestamp); + onDiscoveredContact(ci, true, packet->path_len, packet->path); onContactsFull(); MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact"); return; } - from->id = id; - from->out_path_len = -1; // initially out_path is unknown - from->gps_lat = 0; // initially unknown GPS loc - from->gps_lon = 0; + populateContactFromAdvert(*from, id, parser, timestamp); from->sync_since = 0; - from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand - } - - // update - StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); - from->type = parser.getType(); - if (parser.hasLatLon()) { - from->gps_lat = parser.getIntLat(); - from->gps_lon = parser.getIntLon(); + from->shared_secret_valid = false; } - from->last_advert_timestamp = timestamp; - from->lastmod = getRTCClock()->getCurrentTime(); + // update + StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); + from->type = parser.getType(); + if (parser.hasLatLon()) { + from->gps_lat = parser.getIntLat(); + from->gps_lon = parser.getIntLon(); + } + from->last_advert_timestamp = timestamp; + from->lastmod = getRTCClock()->getCurrentTime(); onDiscoveredContact(*from, is_new, packet->path_len, packet->path); // let UI know } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index bfbf254efb..5387d3dd67 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -89,6 +89,7 @@ class BaseChatMesh : public mesh::Mesh { } void resetContacts() { num_contacts = 0; } + void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp); ContactInfo* allocateContactSlot(); // helper to find slot for new contact // 'UI' concepts, for sub-classes to implement From df6687034a12793db019788575a24e8d0b530269 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 13 Jan 2026 04:27:27 +1100 Subject: [PATCH 256/409] bootstrap RTC from contact.lastmod and improve slot overwrite logic slot overwrite logic can now safely use contact.lastmod to find oldest contact for overwrite --- examples/companion_radio/MyMesh.cpp | 1 + src/helpers/BaseChatMesh.cpp | 23 ++++++++++++++++------- src/helpers/BaseChatMesh.h | 1 + 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 803e3b035e..28d60a0fea 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -862,6 +862,7 @@ void MyMesh::begin(bool has_display) { resetContacts(); _store->loadContacts(this); + bootstrapRTCfromContacts(); addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel _store->loadChannels(this); diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index b68f48056e..98b409622a 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -55,17 +55,29 @@ void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { } } +void BaseChatMesh::bootstrapRTCfromContacts() { + uint32_t latest = 0; + for (int i = 0; i < num_contacts; i++) { + if (contacts[i].lastmod > latest) { + latest = contacts[i].lastmod; + } + } + if (latest != 0) { + getRTCClock()->setCurrentTime(latest + 1); + } +} + ContactInfo* BaseChatMesh::allocateContactSlot() { if (num_contacts < MAX_CONTACTS) { return &contacts[num_contacts++]; } else if (shouldOverwriteWhenFull()) { - // Find oldest non-favourite contact by last_advert_timestamp + // Find oldest non-favourite contact by oldest lastmod timestamp int oldest_idx = -1; - uint32_t oldest_timestamp = 0xFFFFFFFF; + uint32_t oldest_lastmod = 0xFFFFFFFF; for (int i = 0; i < num_contacts; i++) { bool is_favourite = (contacts[i].flags & 0x01) != 0; - if (!is_favourite && contacts[i].last_advert_timestamp < oldest_timestamp) { - oldest_timestamp = contacts[i].last_advert_timestamp; + if (!is_favourite && contacts[i].lastmod < oldest_lastmod) { + oldest_lastmod = contacts[i].lastmod; oldest_idx = i; } } @@ -750,9 +762,6 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { ContactInfo* dest = allocateContactSlot(); if (dest) { *dest = contact; - if (dest->last_advert_timestamp == 0) { // ensure non-zero timestamp to prevent contacts added from discover list being considered 'oldest' - dest->last_advert_timestamp = getRTCClock()->getCurrentTimeUnique(); - } dest->shared_secret_valid = false; // mark shared_secret as needing calculation return true; // success } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 5387d3dd67..fd391b9808 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -88,6 +88,7 @@ class BaseChatMesh : public mesh::Mesh { memset(connections, 0, sizeof(connections)); } + void bootstrapRTCfromContacts(); void resetContacts() { num_contacts = 0; } void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp); ContactInfo* allocateContactSlot(); // helper to find slot for new contact From 11565673c37d0b89aea3c5e5d39f4edb9ede8a33 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Thu, 15 Jan 2026 15:39:44 +0100 Subject: [PATCH 257/409] fix: bump max contacts for v3 companion usb --- variants/heltec_v3/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index dcb2873c0b..6b61eff5d5 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -323,7 +323,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=140 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 From c61fde9328503c2e34677fc7e091c716bdaec2e5 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 16 Jan 2026 12:17:22 +1100 Subject: [PATCH 258/409] always send PUSH_CODE_NEW_ADVERT when advert was not added to contacts[] --- examples/companion_radio/MyMesh.cpp | 2 +- src/helpers/BaseChatMesh.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 28d60a0fea..3d8798b29d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -323,7 +323,7 @@ void MyMesh::onContactsFull() { void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) { if (_serial->isConnected()) { - if (!shouldAutoAddContactType(contact.type) && is_new) { + if (is_new) { writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact); } else { out_frame[0] = PUSH_CODE_ADVERT; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 98b409622a..aebfc1b644 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -133,7 +133,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, } putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); - bool is_new = false; + bool is_new = false; // true = not in contacts[], false = exists in contacts[] if (from == NULL) { if (!shouldAutoAddContactType(parser.getType())) { ContactInfo ci; @@ -142,7 +142,6 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } - is_new = true; from = allocateContactSlot(); if (from == NULL) { ContactInfo ci; From b919119fafaa0ac46810fc1f4f0954eb1c4ec57f Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 16 Jan 2026 13:14:51 +1100 Subject: [PATCH 259/409] only write contacts when changed --- examples/companion_radio/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3d8798b29d..c80e28699b 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -358,7 +358,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(p->path, path, p->path_len); } - dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[] } static int sort_by_recent(const void *a, const void *b) { From 5c7b28f1104b2168b2fb6fd97c4bed3aac0389e3 Mon Sep 17 00:00:00 2001 From: WattleFoxxo Date: Sun, 18 Jan 2026 14:29:50 +1100 Subject: [PATCH 260/409] Change the Station G2 default tx power set the default TX power to 7dBm to avoid illegal power output by default. --- variants/station_g2/platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 1428221d0a..91ef5f7a50 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -16,7 +16,8 @@ build_flags = -D P_LORA_SCLK=12 -D P_LORA_MISO=14 -D P_LORA_MOSI=13 - -D LORA_TX_POWER=19 + -D LORA_TX_POWER=7 ; configured as 7dbm, because the final output will be ~27dbm (~0.5w) if the PA is enabled. + -D MAX_LORA_TX_POWER=19 ; max output without burning out the PA ; -D P_LORA_TX_LED=35 -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 From ed5d2909fc91c3f1769e87a9b0f3350acd9e2bcf Mon Sep 17 00:00:00 2001 From: chrisdavis2110 Date: Sat, 17 Jan 2026 22:54:20 -0800 Subject: [PATCH 261/409] updated variant rak3401 --- boards/rak3401.json | 72 ++++++++++++++++++++++++++ variants/rak3401/RAK3401Board.cpp | 7 +-- variants/rak3401/RAK3401Board.h | 37 +++++--------- variants/rak3401/platformio.ini | 10 +--- variants/rak3401/target.cpp | 8 --- variants/rak3401/variant.cpp | 37 ++++++++------ variants/rak3401/variant.h | 84 ++++++++++++++----------------- 7 files changed, 148 insertions(+), 107 deletions(-) create mode 100644 boards/rak3401.json diff --git a/boards/rak3401.json b/boards/rak3401.json new file mode 100644 index 0000000000..a2816a63b4 --- /dev/null +++ b/boards/rak3401.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "WisCore RAK3401 Board", + "mcu": "nrf52840", + "variant": "WisCore_RAK3401_Board", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": [ + "arduino" + ], + "name": "WisCore RAK3401 Board", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.rakwireless.com", + "vendor": "RAKwireless" +} diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index 50499dffca..b9431c929e 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -20,18 +20,13 @@ void RAK3401Board::begin() { Wire.begin(); - // Enable 3.3V periphery power rail (GPS, IO Module, etc.) pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); - pinMode(SX126X_POWER_EN, OUTPUT); - digitalWrite(SX126X_POWER_EN, HIGH); - delay(10); // give sx1262 some time to power up - #ifdef P_LORA_PA_EN // Initialize RAK13302 1W LoRa transceiver module PA control pin pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN, LOW); // Start with PA disabled delay(10); // Allow PA module to initialize #endif -} +} \ No newline at end of file diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 5188f274aa..609393c338 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -4,45 +4,34 @@ #include #include -// LoRa radio module pins for RAK3401 with RAK13302 (uses SPI1) -#define P_LORA_DIO_1 10 +// LoRa radio module pins for RAK13302 +#define P_LORA_SCLK 3 +#define P_LORA_MISO 29 +#define P_LORA_MOSI 30 #define P_LORA_NSS 26 -#define P_LORA_RESET 4 +#define P_LORA_DIO_1 10 #define P_LORA_BUSY 9 -#define P_LORA_SCLK 3 // SPI1_SCK -#define P_LORA_MISO 29 // SPI1_MISO -#define P_LORA_MOSI 30 // SPI1_MOSI -#define SX126X_POWER_EN 21 +#define P_LORA_RESET 4 +#ifndef P_LORA_PA_EN + #define P_LORA_PA_EN 31 +#endif //#define PIN_GPS_SDA 13 //GPS SDA pin (output option) //#define PIN_GPS_SCL 14 //GPS SCL pin (output option) -//#define PIN_GPS_TX 16 //GPS TX pin -//#define PIN_GPS_RX 15 //GPS RX pin +// #define PIN_GPS_TX 16 //GPS TX pin +// #define PIN_GPS_RX 15 //GPS RX pin #define PIN_GPS_1PPS 17 //GPS PPS pin #define GPS_BAUD_RATE 9600 #define GPS_ADDRESS 0x42 //i2c address for GPS -// RAK13302 1W LoRa transceiver module PA control (WisBlock IO slot) -// The RAK13302 mounts to the IO slot and has an ANT_SW (antenna switch) pin that controls the PA -// This pin must be controlled during transmission to enable the 1W power amplifier -// -// According to RAK13302 datasheet: ANT_SW connects to IO3 on the IO slot -// RAK19007 base board pin mapping: IO3 = pin 31 (also available as AIN1/A1 for analog input) -// -// Default: Pin 31 (IO3) - ANT_SW pin from RAK13302 datasheet -// Override by defining P_LORA_PA_EN in platformio.ini if needed -#ifndef P_LORA_PA_EN - #define P_LORA_PA_EN 31 // ANT_SW pin from RAK13302 datasheet (IO3, pin 31 on RAK19007) -#endif - -#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 + // built-ins #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -// 3.3V periphery enable (GPS, IO Module, etc.) #define PIN_3V3_EN (34) #define WB_IO2 PIN_3V3_EN diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index f1c4bd2df1..30d35d0b40 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -1,20 +1,12 @@ [rak3401] extends = nrf52_base -board = rak4631 +board = rak3401 board_check = true build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I variants/rak3401 - -D RAK_4631 -D RAK_3401 -D RAK13302 - -D RAK_BOARD - -D PIN_BOARD_SCL=14 - -D PIN_BOARD_SDA=13 - -D PIN_GPS_TX=PIN_SERIAL1_RX - -D PIN_GPS_RX=PIN_SERIAL1_TX - -D PIN_GPS_EN=-1 - -D PIN_OLED_RESET=-1 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp index ba827ad182..52f3a3d591 100644 --- a/variants/rak3401/target.cpp +++ b/variants/rak3401/target.cpp @@ -17,8 +17,6 @@ RAK3401Board board; #endif #endif -// RAK3401 uses SPI1 for the RAK13302 LoRa module -// Note: nRF52 doesn't have a separate SPI1 object, so we use SPI but configure it with SPI1 pins 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); @@ -36,12 +34,6 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); bool radio_init() { rtc_clock.begin(Wire); - - // Configure SPI with SPI1 pins for RAK13302 - // nRF52 uses the same SPI peripheral but with different pin assignments - SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); - SPI.begin(); - return radio.std_init(&SPI); } diff --git a/variants/rak3401/variant.cpp b/variants/rak3401/variant.cpp index db55920c20..d562189f9b 100644 --- a/variants/rak3401/variant.cpp +++ b/variants/rak3401/variant.cpp @@ -7,37 +7,46 @@ modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "variant.h" -#include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // P0 + 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , + 8 , 9 , 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, -const uint32_t g_ADigitalPinMap[] = { - // P0 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; void initVariant() { - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); - // 3V3 Power Rail - pinMode(PIN_3V3_EN, OUTPUT); - digitalWrite(PIN_3V3_EN, HIGH); + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); } diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index 03e9c2a8ab..9c18224798 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -34,7 +34,8 @@ #include "WVariant.h" #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif // __cplusplus // Number of pins defined in PinDescription array @@ -58,8 +59,8 @@ extern "C" { /* * Analog pins */ -#define PIN_A0 (5) -#define PIN_A1 (31) +#define PIN_A0 (5) //(3) +#define PIN_A1 (31) //(4) #define PIN_A2 (28) #define PIN_A3 (29) #define PIN_A4 (30) @@ -67,14 +68,14 @@ extern "C" { #define PIN_A6 (0xff) #define PIN_A7 (0xff) -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -static const uint8_t A6 = PIN_A6; -static const uint8_t A7 = PIN_A7; + static const uint8_t A0 = PIN_A0; + static const uint8_t A1 = PIN_A1; + static const uint8_t A2 = PIN_A2; + static const uint8_t A3 = PIN_A3; + static const uint8_t A4 = PIN_A4; + static const uint8_t A5 = PIN_A5; + static const uint8_t A6 = PIN_A6; + static const uint8_t A7 = PIN_A7; #define ADC_RESOLUTION 14 // Other pins @@ -92,6 +93,7 @@ static const uint8_t AREF = PIN_AREF; /* * Serial interfaces */ +// TXD1 RXD1 on Base Board #define PIN_SERIAL1_RX (15) #define PIN_SERIAL1_TX (16) @@ -108,15 +110,14 @@ static const uint8_t AREF = PIN_AREF; #define PIN_SPI_MOSI (44) #define PIN_SPI_SCK (43) -// SPI1 pins for RAK13302 1W LoRa module -#define PIN_SPI1_MISO (29) // (0 + 29) -#define PIN_SPI1_MOSI (30) // (0 + 30) -#define PIN_SPI1_SCK (3) // (0 + 3) +#define PIN_SPI1_MISO (29) +#define PIN_SPI1_MOSI (30) +#define PIN_SPI1_SCK (3) -static const uint8_t SS = 42; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; + static const uint8_t SS = 42; + static const uint8_t MOSI = PIN_SPI_MOSI; + static const uint8_t MISO = PIN_SPI_MISO; + static const uint8_t SCK = PIN_SPI_SCK; /* * Wire Interfaces @@ -127,6 +128,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_WIRE_SCL (WB_I2C1_SCL) // QSPI Pins +// QSPI occupied by GPIO's #define PIN_QSPI_SCK 3 #define PIN_QSPI_CS 26 #define PIN_QSPI_IO0 30 @@ -135,35 +137,25 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_QSPI_IO3 2 // On-board QSPI Flash +// No onboard flash #define EXTERNAL_FLASH_DEVICES IS25LP080D #define EXTERNAL_FLASH_USE_QSPI -// RAK13302 1W LoRa transceiver module (uses SPI1) -// LoRa radio module pins for RAK3401 with RAK13302 -#define P_LORA_DIO_1 10 -#define P_LORA_NSS 26 -#define P_LORA_RESET 4 -#define P_LORA_BUSY 9 -#define P_LORA_SCLK PIN_SPI1_SCK // 3 -#define P_LORA_MISO PIN_SPI1_MISO // 29 -#define P_LORA_MOSI PIN_SPI1_MOSI // 30 -#define SX126X_POWER_EN 21 - -// RAK13302 1W LoRa transceiver module PA control (WisBlock IO slot) -// The RAK13302 mounts to the IO slot and has an ANT_SW (antenna switch) pin that controls the PA -// This pin must be controlled during transmission to enable the 1W power amplifier -// -// According to RAK13302 datasheet: ANT_SW connects to IO3 on the IO slot -// RAK19007 base board pin mapping: IO3 = pin 31 (also available as AIN1/A1 for analog input) -// -// Default: Pin 31 (IO3) - ANT_SW pin from RAK13302 datasheet -// Override by defining P_LORA_PA_EN in platformio.ini if needed -#ifndef P_LORA_PA_EN - #define P_LORA_PA_EN 31 // ANT_SW pin from RAK13302 datasheet (IO3, pin 31 on RAK19007) -#endif +#define P_LORA_SCK PIN_SPI1_SCK +#define P_LORA_MISO PIN_SPI1_MISO +#define P_LORA_MOSI PIN_SPI1_MOSI +#define P_LORA_CS 26 + +#define USE_SX1262 +#define SX126X_CS (26) +#define SX126X_DIO1 (10) +#define SX126X_BUSY (9) +#define SX126X_RESET (4) -#define SX126X_DIO2_AS_RF_SWITCH true -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_POWER_EN (21) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // enables 3.3V periphery like GPS or IO Module // Do not toggle this for GPS power savings @@ -178,8 +170,8 @@ static const uint8_t SCK = PIN_SPI_SCK; // Power is on the controllable 3V3_S rail #define PIN_GPS_PPS (17) // Pulse per second input from the GPS -#define GPS_RX_PIN PIN_SERIAL1_RX -#define GPS_TX_PIN PIN_SERIAL1_TX +#define PIN_GPS_RX PIN_SERIAL1_RX +#define PIN_GPS_TX PIN_SERIAL1_TX // Battery // The battery sense is hooked to pin A0 (5) From e51a2d1ba0b57df25893a50bc6c8f7d649fdf96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Fesser?= Date: Mon, 19 Jan 2026 21:39:01 +0100 Subject: [PATCH 262/409] Update T114 I2C pins --- variants/heltec_t114/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index b3f760bbb0..7b18585d12 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -50,8 +50,8 @@ //////////////////////////////////////////////////////////////////////////////// // I2C pin definition -#define PIN_WIRE_SDA (26) // P0.26 -#define PIN_WIRE_SCL (27) // P0.27 +#define PIN_WIRE_SDA (16) // P0.16 +#define PIN_WIRE_SCL (13) // P0.13 //////////////////////////////////////////////////////////////////////////////// // SPI pin definition From a7cadc8e4461ad709f62166096d4b00f12f1c2be Mon Sep 17 00:00:00 2001 From: Miguel de Matos <11491485+Snayler@users.noreply.github.com> Date: Tue, 20 Jan 2026 01:52:45 +0000 Subject: [PATCH 263/409] Fix Serial and TX LED not working on Heltec Wireless Paper V1.2 As described on #1276, tested and working on my heltec wireless paper v1.2 --- variants/heltec_wireless_paper/platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 9cf7615371..f0bca86047 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -5,7 +5,7 @@ build_flags = ${esp32_base.build_flags} -I variants/heltec_wireless_paper -D HELTEC_WIRELESS_PAPER - -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial + ;-D ARDUINO_USB_CDC_ON_BOOT=1 ; this breaks Serial -D P_LORA_DIO_1=14 -D P_LORA_NSS=8 -D P_LORA_RESET=RADIOLIB_NC @@ -17,8 +17,8 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D P_LORA_TX_LED=18 - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 + ;-D PIN_BOARD_SDA=17 + ;-D PIN_BOARD_SCL=18 ; same GPIO as P_LORA_TX_LED -D PIN_USER_BTN=0 -D PIN_VEXT_EN=45 -D PIN_VBAT_READ=20 @@ -139,4 +139,4 @@ build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} +<../examples/simple_room_server> lib_deps = ${Heltec_Wireless_Paper_base.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} From d68bc74514ec9a965c18f3c13b5fd79b05eb667e Mon Sep 17 00:00:00 2001 From: nakoeppen Date: Tue, 20 Jan 2026 20:19:10 -0600 Subject: [PATCH 264/409] Remove _serial->isConnected() logic from buzzer notifications --- examples/companion_radio/MyMesh.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c80e28699b..c11c70d7ca 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -330,11 +330,10 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); } - } else { + } #ifdef DISPLAY_CLASS - if (_ui) _ui->notify(UIEventType::newContactMessage); + if (_ui && !_prefs.buzzer_quiet) _ui->notify(UIEventType::newContactMessage); //buzz if enabled #endif - } // add inbound-path to mem cache if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid @@ -441,9 +440,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN; if (should_display && _ui) { _ui->newMsg(path_len, from.name, text, offline_queue_len); - if (!_serial->isConnected()) { - _ui->notify(UIEventType::contactMessage); - } + if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::contactMessage); //buzz if enabled } #endif } @@ -528,11 +525,8 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe uint8_t frame[1]; frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle' _serial->writeFrame(frame, 1); - } else { -#ifdef DISPLAY_CLASS - if (_ui) _ui->notify(UIEventType::channelMessage); -#endif } + #ifdef DISPLAY_CLASS // Get the channel name from the channel index const char *channel_name = "Unknown"; @@ -540,7 +534,10 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe if (getChannel(channel_idx, channel_details)) { channel_name = channel_details.name; } - if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len); + if (_ui) { + _ui->newMsg(path_len, channel_name, text, offline_queue_len); + if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::channelMessage); //buzz if enabled + } #endif } @@ -799,6 +796,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; + _prefs.buzzer_quiet = 0; _prefs.gps_enabled = 0; // GPS disabled by default _prefs.gps_interval = 0; // No automatic GPS updates by default //_prefs.rx_delay_base = 10.0f; enable once new algo fixed @@ -838,6 +836,7 @@ void MyMesh::begin(bool has_display) { _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); + _prefs.buzzer_quiet = constrain(_prefs.buzzer_quiet, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours From 46e4cc06e39043c74cd4d164cc2aedb0f5bbeccf Mon Sep 17 00:00:00 2001 From: Socalix <48040807+Socalix@users.noreply.github.com> Date: Wed, 21 Jan 2026 21:12:54 -0600 Subject: [PATCH 265/409] Revert boosted gain flag to original --- variants/heltec_v4/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 9ab3e1625a..258a99673c 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -31,7 +31,7 @@ build_flags = -D SX126X_DIO2_AS_RF_SWITCH=true ; GC1109 CTX is controlled by SX1262 DIO2 -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -; -D SX126X_RX_BOOSTED_GAIN=1 ; Turned off for improved RX + -D SX126X_RX_BOOSTED_GAIN=1 ; In some cases, commenting this out will improve RX -D PIN_GPS_RX=38 -D PIN_GPS_TX=39 -D PIN_GPS_RESET=42 From b09ddfc5e18f0e73fdd14f603f6c90cfeb813ec2 Mon Sep 17 00:00:00 2001 From: taco Date: Thu, 22 Jan 2026 14:41:07 +1100 Subject: [PATCH 266/409] thinknode m1: add missing getLocationProvider() override --- variants/thinknode_m1/target.h | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index 1e4e1381f7..8425369d8f 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -22,6 +22,7 @@ class ThinkNodeM1SensorManager : public SensorManager { void stop_gps(); public: ThinkNodeM1SensorManager(LocationProvider &location): _location(&location) { } + LocationProvider* getLocationProvider() override { return _location; } bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; void loop() override; From ea85486dca3bd912a974109ec01b05c753464492 Mon Sep 17 00:00:00 2001 From: taco Date: Thu, 22 Jan 2026 14:42:08 +1100 Subject: [PATCH 267/409] thinknode m1: add missing GPS page to new UI --- variants/thinknode_m1/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index ade487e9dc..397bf8e30f 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -83,6 +83,7 @@ build_flags = -D PIN_BUZZER=6 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 -D QSPIFLASH=1 + -D ENV_INCLUDE_GPS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M1.build_src_filter} From 36f230d074f6de62049f59b3804278f05dd1017d Mon Sep 17 00:00:00 2001 From: taco Date: Thu, 22 Jan 2026 14:42:43 +1100 Subject: [PATCH 268/409] thinknode m1: allow GPS to sync clock --- variants/thinknode_m1/target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index 2b04d7c6e0..c3b1abc21e 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -11,7 +11,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); ThinkNodeM1SensorManager sensors = ThinkNodeM1SensorManager(nmea); #ifdef DISPLAY_CLASS From fc61018d4daabed08ccf5e220bcc29549f4313cd Mon Sep 17 00:00:00 2001 From: Quency-D Date: Fri, 23 Jan 2026 10:45:13 +0800 Subject: [PATCH 269/409] Fix the issue of inconsistent I2C usage in the environmental sensor. --- src/helpers/sensors/EnvironmentSensorManager.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b7238def9c..8471d80db0 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -42,7 +42,7 @@ static Adafruit_BME280 BME280; #endif #define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level #include -static Adafruit_BMP280 BMP280; +static Adafruit_BMP280 BMP280(TELEM_WIRE); #endif #if ENV_INCLUDE_SHTC3 @@ -58,6 +58,7 @@ static SensirionI2cSht4x SHT4X; #if ENV_INCLUDE_LPS22HB #include +LPS22HBClass LPS22HB(*TELEM_WIRE); #endif #if ENV_INCLUDE_INA3221 @@ -218,7 +219,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_SHTC3 - if (SHTC3.begin()) { + if (SHTC3.begin(TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found sensor: SHTC3"); SHTC3_initialized = true; } else { @@ -243,7 +244,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_LPS22HB - if (BARO.begin()) { + if (LPS22HB.begin()) { MESH_DEBUG_PRINTLN("Found sensor: LPS22HB"); LPS22HB_initialized = true; } else { @@ -407,8 +408,8 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen #if ENV_INCLUDE_LPS22HB if (LPS22HB_initialized) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure() * 10); // convert kPa to hPa + telemetry.addTemperature(TELEM_CHANNEL_SELF, LPS22HB.readTemperature()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, LPS22HB.readPressure() * 10); // convert kPa to hPa } #endif From 3c27132914eb9b92ad39310e12106143619134b3 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 23 Jan 2026 15:53:58 +1100 Subject: [PATCH 270/409] * T1000e BLE - default node name is now the MAC address --- examples/companion_radio/MyMesh.cpp | 8 ++++---- examples/companion_radio/main.cpp | 8 ++------ src/helpers/esp32/SerialBLEInterface.cpp | 14 ++++++++++++-- src/helpers/esp32/SerialBLEInterface.h | 8 +++++++- src/helpers/nrf52/SerialBLEInterface.cpp | 16 +++++++++++++--- src/helpers/nrf52/SerialBLEInterface.h | 9 ++++++++- variants/t1000-e/platformio.ini | 1 + 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c11c70d7ca..9de91e4585 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -815,14 +815,14 @@ void MyMesh::begin(bool has_display) { _store->saveMainIdentity(self_id); } +// if name is provided as a build flag, use that as default node name instead +#ifdef ADVERT_NAME + strcpy(_prefs.node_name, ADVERT_NAME); +#else // use hex of first 4 bytes of identity public key as default node name char pub_key_hex[10]; mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4); strcpy(_prefs.node_name, pub_key_hex); - -// if name is provided as a build flag, use that as default node name instead -#ifdef ADVERT_NAME - strcpy(_prefs.node_name, ADVERT_NAME); #endif // load persisted prefs diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d93..7e636acee5 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -151,9 +151,7 @@ void setup() { ); #ifdef BLE_PIN_CODE - char dev_name[32+16]; - sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName()); - serial_interface.begin(dev_name, the_mesh.getBLEPin()); + serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); #else serial_interface.begin(Serial); #endif @@ -199,9 +197,7 @@ void setup() { WiFi.begin(WIFI_SSID, WIFI_PWD); serial_interface.begin(TCP_PORT); #elif defined(BLE_PIN_CODE) - char dev_name[32+16]; - sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName()); - serial_interface.begin(dev_name, the_mesh.getBLEPin()); + serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); #elif defined(SERIAL_RX) companion_serial.setPins(SERIAL_RX, SERIAL_TX); companion_serial.begin(115200); diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index 7ec937238c..eccfeca684 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -9,11 +9,21 @@ #define ADVERT_RESTART_DELAY 1000 // millis -void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { +void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) { _pin_code = pin_code; + if (strcmp(name, "@@MAC") == 0) { + uint8_t addr[8]; + memset(addr, 0, sizeof(addr)); + esp_efuse_mac_get_default(addr); + sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param) + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + } + char dev_name[32+16]; + sprintf(dev_name, "%s%s", prefix, name); + // Create the BLE Device - BLEDevice::init(device_name); + BLEDevice::init(dev_name); BLEDevice::setSecurityCallbacks(this); BLEDevice::setMTU(MAX_FRAME_SIZE); diff --git a/src/helpers/esp32/SerialBLEInterface.h b/src/helpers/esp32/SerialBLEInterface.h index 29ad897ae7..965e90fd19 100644 --- a/src/helpers/esp32/SerialBLEInterface.h +++ b/src/helpers/esp32/SerialBLEInterface.h @@ -61,7 +61,13 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE send_queue_len = recv_queue_len = 0; } - void begin(const char* device_name, uint32_t pin_code); + /** + * init the BLE interface. + * @param prefix a prefix for the device name + * @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned + * @param pin_code the BLE security pin + */ + void begin(const char* prefix, char* name, uint32_t pin_code); // BaseSerialInterface methods void enable() override; diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index eb1e90bb70..5648707e6a 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -123,7 +123,7 @@ void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) { } } -void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { +void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) { instance = this; char charpin[20]; @@ -133,7 +133,17 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { // Bluefruit.autoConnLed(false); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); Bluefruit.begin(); - + + char dev_name[32+16]; + if (strcmp(name, "@@MAC") == 0) { + ble_gap_addr_t addr; + if (sd_ble_gap_addr_get(&addr) == NRF_SUCCESS) { + sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param) + addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2], addr.addr[1], addr.addr[0]); + } + } + sprintf(dev_name, "%s%s", prefix, name); + // Connection interval units: 1.25ms, supervision timeout units: 10ms ble_gap_conn_params_t ppcp_params; ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL; @@ -153,7 +163,7 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { } Bluefruit.setTxPower(BLE_TX_POWER); - Bluefruit.setName(device_name); + Bluefruit.setName(dev_name); Bluefruit.Security.setMITM(true); Bluefruit.Security.setPIN(charpin); diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index 25968d78fd..e2fc6cb95f 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -52,7 +52,14 @@ class SerialBLEInterface : public BaseSerialInterface { recv_queue_len = 0; } - void begin(const char* device_name, uint32_t pin_code); + /** + * init the BLE interface. + * @param prefix a prefix for the device name + * @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned + * @param pin_code the BLE security pin + */ + void begin(const char* prefix, char* name, uint32_t pin_code); + void disconnect(); void enable() override; void disable() override; diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 555b182fb4..ac92930861 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -107,6 +107,7 @@ build_flags = ${t1000-e.build_flags} -D DISPLAY_CLASS=NullDisplayDriver -D PIN_BUZZER=25 -D PIN_BUZZER_EN=37 ; P1/5 - required for T1000-E + -D ADVERT_NAME='"@@MAC"' build_src_filter = ${t1000-e.build_src_filter} + + From 1f59e5288049cc9f6601bfc7728ab3205c0c25c3 Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:18:41 +1100 Subject: [PATCH 271/409] nRF52840 Power Management - Phase 1 - Boot Low VBAT Voltage Lockout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added NRF52840 power management core functionality: - Boot‑voltage lockout - Initial support for shutdown/reset reason storage and capture (via RESETREAS/GPREGRET2) - LPCOMP wake (for voltage-driven shutdowns) - VBUS wake (for voltage-driven shutdowns) - Per-board shutdown handler for board-specific tasks - Exposed CLI queries for power‑management status in CommonCLI.cpp - Added documentation in docs/nrf52_power_management.md. - Enabled power management support in Xiao nRF52840, RAK4631, Heltec T114 boards --- docs/nrf52_power_management.md | 213 ++++++++++++++++++++++++ src/MeshCore.h | 8 + src/helpers/CommonCLI.cpp | 27 +++ src/helpers/NRF52Board.cpp | 217 +++++++++++++++++++++++++ src/helpers/NRF52Board.h | 43 +++++ variants/heltec_t114/T114Board.cpp | 34 ++++ variants/heltec_t114/T114Board.h | 13 +- variants/heltec_t114/platformio.ini | 1 + variants/heltec_t114/variant.h | 8 + variants/rak4631/RAK4631Board.cpp | 27 +++ variants/rak4631/RAK4631Board.h | 5 + variants/rak4631/platformio.ini | 1 + variants/rak4631/variant.h | 8 + variants/xiao_nrf52/XiaoNrf52Board.cpp | 47 +++++- variants/xiao_nrf52/XiaoNrf52Board.h | 23 +-- variants/xiao_nrf52/platformio.ini | 1 + variants/xiao_nrf52/variant.h | 15 ++ 17 files changed, 667 insertions(+), 24 deletions(-) create mode 100644 docs/nrf52_power_management.md diff --git a/docs/nrf52_power_management.md b/docs/nrf52_power_management.md new file mode 100644 index 0000000000..ebe9bbbea9 --- /dev/null +++ b/docs/nrf52_power_management.md @@ -0,0 +1,213 @@ +# nRF52 Power Management + +## Overview + +The nRF52 Power Management module provides battery protection features to prevent over-discharge, minimise likelihood of brownout and flash corruption conditions existing, and enable safe voltage-based recovery. + +## Features + +### Boot Voltage Protection +- Checks battery voltage immediately after boot and before mesh operations commence +- If voltage is below a configurable threshold (e.g., 3300mV), the device configures voltage wake (LPCOMP + VBUS) and enters protective shutdown (SYSTEMOFF) +- Prevents boot loops when battery is critically low +- Skipped when external power (USB VBUS) is detected + +### Voltage Wake (LPCOMP + VBUS) +- Configures the nRF52's Low Power Comparator (LPCOMP) before entering SYSTEMOFF +- Enables USB VBUS detection so external power can wake the device +- Device automatically wakes when battery voltage rises above recovery threshold or when VBUS is detected + +### Early Boot Register Capture +- Captures RESETREAS (reset reason) and GPREGRET2 (shutdown reason) before SystemInit() clears them +- Allows firmware to determine why it booted (cold boot, watchdog, LPCOMP wake, etc.) +- Allows firmware to determine why it last shut down (user request, low voltage, boot protection) + +### Shutdown Reason Tracking +Shutdown reason codes (stored in GPREGRET2): +| Code | Name | Description | +|------|------|-------------| +| 0x00 | NONE | Normal boot / no previous shutdown | +| 0x4C | LOW_VOLTAGE | Runtime low voltage threshold reached | +| 0x55 | USER | User requested powerOff() | +| 0x42 | BOOT_PROTECT | Boot voltage protection triggered | + +## Supported Boards + +| Board | Implemented | LPCOMP wake | VBUS wake | +|-------|-------------|-------------|-----------| +| Seeed Studio XIAO nRF52840 (`xiao_nrf52`) | Yes | Yes | Yes | +| RAK4631 (`rak4631`) | Yes | Yes | Yes | +| Heltec T114 (`heltec_t114`) | Yes | Yes | Yes | +| Promicro nRF52840 | No | No | No | +| RAK WisMesh Tag | No | No | No | +| Heltec Mesh Solar | No | No | No | +| LilyGo T-Echo / T-Echo Lite | No | No | No | +| SenseCAP Solar | No | No | No | +| WIO Tracker L1 / L1 E-Ink | No | No | No | +| WIO WM1110 | No | No | No | +| Mesh Pocket | No | No | No | +| Nano G2 Ultra | No | No | No | +| ThinkNode M1/M3/M6 | No | No | No | +| T1000-E | No | No | No | +| Ikoka Nano/Stick/Handheld (nRF) | No | No | No | +| Keepteen LT1 | No | No | No | +| Minewsemi ME25LS01 | No | No | No | + +Notes: +- "Implemented" reflects Phase 1 (boot lockout + shutdown reason capture). +- User power-off on Heltec T114 does not enable LPCOMP wake. +- VBUS detection is used to skip boot lockout on external power, and VBUS wake is configured alongside LPCOMP when supported hardware exposes VBUS to the nRF52. + +## Technical Details + +### Architecture + +The power management functionality is integrated into the `NRF52Board` base class in `src/helpers/NRF52Board.cpp`. Board variants provide hardware-specific configuration via a `PowerMgtConfig` struct and override `initiateShutdown(uint8_t reason)` to perform board-specific power-down work and conditionally enable voltage wake (LPCOMP + VBUS). + +### Early Boot Capture + +A static constructor with priority 101 in `NRF52Board.cpp` captures the RESETREAS and GPREGRET2 registers before: +- SystemInit() (priority 102) - which clears RESETREAS +- Static C++ constructors (default priority 65535) + +This ensures we capture the true reset reason before any initialisation code runs. + +### Board Implementation + +To enable power management on a board variant: + +1. **Enable in platformio.ini**: + ```ini + -D NRF52_POWER_MANAGEMENT + ``` + +2. **Define configuration in variant.h**: + ```c + #define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) + #define PWRMGT_LPCOMP_AIN 7 // AIN channel for voltage sensing + #define PWRMGT_LPCOMP_REFSEL 2 // REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) + ``` + +3. **Implement in board .cpp file**: + ```cpp + #ifdef NRF52_POWER_MANAGEMENT + const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK + }; + + void MyBoard::initiateShutdown(uint8_t reason) { + // Board-specific shutdown preparation (e.g., disable peripherals) + bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT); + + if (enable_lpcomp) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); + } + #endif + + void MyBoard::begin() { + NRF52Board::begin(); // or NRF52BoardDCDC::begin() + // ... board setup ... + + #ifdef NRF52_POWER_MANAGEMENT + checkBootVoltage(&power_config); + #endif + } + ``` + + For user-initiated shutdowns, `powerOff()` remains board-specific. Power management only arms LPCOMP for automated shutdown reasons (boot protection/low voltage). + +4. **Declare override in board .h file**: + ```cpp + #ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; + #endif + ``` + +### Voltage Wake Configuration + +The LPCOMP (Low Power Comparator) is configured to: +- Monitor the specified AIN channel (0-7 corresponding to P0.02-P0.05, P0.28-P0.31) +- Compare against VDD fraction reference (REFSEL: 0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) +- Detect UP events (voltage rising above threshold) +- Use 50mV hysteresis for noise immunity +- Wake the device from SYSTEMOFF when triggered + +VBUS wake is enabled via the POWER peripheral USBDETECTED event whenever `configureVoltageWake()` is used. This requires USB VBUS to be routed to the nRF52 (typical on nRF52840 boards with native USB). + +**LPCOMP Reference Selection (PWRMGT_LPCOMP_REFSEL)**: +| REFSEL | Fraction | VBAT @ 1M/1M divider (VDD=3.0-3.3) | VBAT @ 1.5M/1M divider (VDD=3.0-3.3) | +|--------|----------|------------------------------------|--------------------------------------| +| 0 | 1/8 | 0.75-0.82 V | 0.94-1.03 V | +| 1 | 2/8 | 1.50-1.65 V | 1.88-2.06 V | +| 2 | 3/8 | 2.25-2.47 V | 2.81-3.09 V | +| 3 | 4/8 | 3.00-3.30 V | 3.75-4.12 V | +| 4 | 5/8 | 3.75-4.12 V | 4.69-5.16 V | +| 5 | 6/8 | 4.50-4.95 V | 5.62-6.19 V | +| 6 | 7/8 | 5.25-5.77 V | 6.56-7.22 V | +| 7 | ARef | - | - | +| 8 | 1/16 | 0.38-0.41 V | 0.47-0.52 V | +| 9 | 3/16 | 1.12-1.24 V | 1.41-1.55 V | +| 10 | 5/16 | 1.88-2.06 V | 2.34-2.58 V | +| 11 | 7/16 | 2.62-2.89 V | 3.28-3.61 V | +| 12 | 9/16 | 3.38-3.71 V | 4.22-4.64 V | +| 13 | 11/16 | 4.12-4.54 V | 5.16-5.67 V | +| 14 | 13/16 | 4.88-5.36 V | 6.09-6.70 V | +| 15 | 15/16 | 5.62-6.19 V | 7.03-7.73 V | + +**Important**: For boards with a voltage divider on the battery sense pin, LPCOMP measures the divided voltage. Use: +`VBAT_threshold ≈ (VDD * fraction) * divider_scale`, where `divider_scale = (Rtop + Rbottom) / Rbottom` (e.g., 2.0 for 1M/1M, 2.5 for 1.5M/1M, 3.0 for XIAO). + +### SoftDevice Compatibility + +The power management code checks whether SoftDevice is enabled and uses the appropriate API: +- When SD enabled: `sd_power_*` functions +- When SD disabled: Direct register access (NRF_POWER->*) + +This ensures compatibility regardless of BLE stack state. + +## CLI Commands + +Power management status can be queried via the CLI: + +| Command | Description | +|---------|-------------| +| `get pwrmgt.support` | Returns "supported" or "unsupported" | +| `get pwrmgt.source` | Returns current power source - "battery" or "external" (5V/USB power) | +| `get pwrmgt.bootreason` | Returns reset and shutdown reason strings | +| `get pwrmgt.bootmv` | Returns boot voltage in millivolts | + +On boards without power management enabled, all commands except `get pwrmgt.support` return: +``` +ERROR: Power management not supported +``` + +## Debug Output + +When `MESH_DEBUG=1` is enabled, the power management module outputs: +``` +DEBUG: PWRMGT: Reset = Wake from LPCOMP (0x20000); Shutdown = Low Voltage (0x4C) +DEBUG: PWRMGT: Boot voltage = 3450 mV (threshold = 3300 mV) +DEBUG: PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD) +``` + +## Phase 2 (Planned) + +- Runtime voltage monitoring +- Voltage state machine (Normal -> Warning -> Critical -> Shutdown) +- Configurable thresholds +- Load shedding callbacks for power reduction +- Deep sleep integration +- Scheduled wake-up +- Extended sleep with periodic monitoring + +## References + +- [nRF52840 Product Specification - POWER](https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html) +- [nRF52840 Product Specification - LPCOMP](https://infocenter.nordicsemi.com/topic/ps_nrf52840/lpcomp.html) +- [SoftDevice S140 API - Power Management](https://infocenter.nordicsemi.com/topic/sdk_nrf5_v17.1.0/group__nrf__sdm__api.html) diff --git a/src/MeshCore.h b/src/MeshCore.h index 718660d3bd..f194cdeb43 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -56,6 +56,14 @@ class MainBoard { virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported + + // Power management interface (boards with power management override these) + virtual bool isExternalPowered() { return false; } + virtual uint16_t getBootVoltage() { return 0; } + virtual uint32_t getResetReason() const { return 0; } + virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; } + virtual uint8_t getShutdownReason() const { return 0; } + virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; } }; /** diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 2fc93006b0..6dac9fff0a 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -364,6 +364,33 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { sprintf(reply, "> %.3f", adc_mult); } + // Power management commands + } else if (memcmp(config, "pwrmgt.support", 14) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + strcpy(reply, "> supported"); +#else + strcpy(reply, "> unsupported"); +#endif + } else if (memcmp(config, "pwrmgt.source", 13) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery"); +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif + } else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + sprintf(reply, "> Reset: %s; Shutdown: %s", + _board->getResetReasonString(_board->getResetReason()), + _board->getShutdownReasonString(_board->getShutdownReason())); +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif + } else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + sprintf(reply, "> %u mV", _board->getBootVoltage()); +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif } else { sprintf(reply, "??: %s", config); } diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index c0d58314e2..1303d5be69 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -2,6 +2,7 @@ #include "NRF52Board.h" #include +#include static BLEDfu bledfu; @@ -21,6 +22,222 @@ void NRF52Board::begin() { startup_reason = BD_STARTUP_NORMAL; } +#ifdef NRF52_POWER_MANAGEMENT +#include "nrf.h" + +// Power Management global variables +uint32_t g_nrf52_reset_reason = 0; // Reset/Startup reason +uint8_t g_nrf52_shutdown_reason = 0; // Shutdown reason + +// Early constructor - runs before SystemInit() clears the registers +// Priority 101 ensures this runs before SystemInit (102) and before +// any C++ static constructors (default 65535) +static void __attribute__((constructor(101))) nrf52_early_reset_capture() { + g_nrf52_reset_reason = NRF_POWER->RESETREAS; + g_nrf52_shutdown_reason = NRF_POWER->GPREGRET2; +} + +void NRF52Board::initPowerMgr() { + // Copy early-captured register values + reset_reason = g_nrf52_reset_reason; + shutdown_reason = g_nrf52_shutdown_reason; + boot_voltage_mv = 0; // Will be set by checkBootVoltage() + + // Clear registers for next boot + // Note: At this point SoftDevice may or may not be enabled + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_reset_reason_clr(0xFFFFFFFF); + sd_power_gpregret_clr(1, 0xFF); + } else { + NRF_POWER->RESETREAS = 0xFFFFFFFF; // Write 1s to clear + NRF_POWER->GPREGRET2 = 0; + } + + // Log reset/shutdown info + if (shutdown_reason != SHUTDOWN_REASON_NONE) { + MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX); Shutdown = %s (0x%02X)", + getResetReasonString(reset_reason), (unsigned long)reset_reason, + getShutdownReasonString(shutdown_reason), shutdown_reason); + } else { + MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX)", + getResetReasonString(reset_reason), (unsigned long)reset_reason); + } +} + +bool NRF52Board::isExternalPowered() { + // Check if SoftDevice is enabled before using its API + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + + if (sd_enabled) { + uint32_t usb_status; + sd_power_usbregstatus_get(&usb_status); + return (usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0; + } else { + return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0; + } +} + +const char* NRF52Board::getResetReasonString(uint32_t reason) { + if (reason & POWER_RESETREAS_RESETPIN_Msk) return "Reset Pin"; + if (reason & POWER_RESETREAS_DOG_Msk) return "Watchdog"; + if (reason & POWER_RESETREAS_SREQ_Msk) return "Soft Reset"; + if (reason & POWER_RESETREAS_LOCKUP_Msk) return "CPU Lockup"; + #ifdef POWER_RESETREAS_LPCOMP_Msk + if (reason & POWER_RESETREAS_LPCOMP_Msk) return "Wake from LPCOMP"; + #endif + #ifdef POWER_RESETREAS_VBUS_Msk + if (reason & POWER_RESETREAS_VBUS_Msk) return "Wake from VBUS"; + #endif + #ifdef POWER_RESETREAS_OFF_Msk + if (reason & POWER_RESETREAS_OFF_Msk) return "Wake from GPIO"; + #endif + #ifdef POWER_RESETREAS_DIF_Msk + if (reason & POWER_RESETREAS_DIF_Msk) return "Debug Interface"; + #endif + return "Cold Boot"; +} + +const char* NRF52Board::getShutdownReasonString(uint8_t reason) { + switch (reason) { + case SHUTDOWN_REASON_LOW_VOLTAGE: return "Low Voltage"; + case SHUTDOWN_REASON_USER: return "User Request"; + case SHUTDOWN_REASON_BOOT_PROTECT: return "Boot Protection"; + } + return "Unknown"; +} + +bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) { + initPowerMgr(); + + // Read boot voltage + boot_voltage_mv = getBattMilliVolts(); + + if (config->voltage_bootlock == 0) return true; // Protection disabled + + // Skip check if externally powered + if (isExternalPowered()) { + MESH_DEBUG_PRINTLN("PWRMGT: Boot check skipped (external power)"); + boot_voltage_mv = getBattMilliVolts(); + return true; + } + + MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage = %u mV (threshold = %u mV)", + boot_voltage_mv, config->voltage_bootlock); + + // Only trigger shutdown if reading is valid (>1000mV) AND below threshold + // This prevents spurious shutdowns on ADC glitches or uninitialized reads + if (boot_voltage_mv > 1000 && boot_voltage_mv < config->voltage_bootlock) { + MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage too low - entering protective shutdown"); + + initiateShutdown(SHUTDOWN_REASON_BOOT_PROTECT); + return false; // Should never reach this + } + + return true; +} + +void NRF52Board::initiateShutdown(uint8_t reason) { + enterSystemOff(reason); +} + +void NRF52Board::enterSystemOff(uint8_t reason) { + MESH_DEBUG_PRINTLN("PWRMGT: Entering SYSTEMOFF (%s)", getShutdownReasonString(reason)); + + // Record shutdown reason in GPREGRET2 + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_gpregret_clr(1, 0xFF); + sd_power_gpregret_set(1, reason); + } else { + NRF_POWER->GPREGRET2 = reason; + } + + // Flush serial buffers + Serial.flush(); + delay(100); + + // Enter SYSTEMOFF + if (sd_enabled) { + uint32_t err = sd_power_system_off(); + if (err == NRF_ERROR_SOFTDEVICE_NOT_ENABLED) { //SoftDevice not enabled + sd_enabled = 0; + } + } + + if (!sd_enabled) { + // SoftDevice not available; write directly to POWER->SYSTEMOFF + NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter; + } + + // If we get here, something went wrong. Reset to recover. + NVIC_SystemReset(); +} + +void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) { + // LPCOMP is not managed by SoftDevice - direct register access required + // Halt and disable before reconfiguration + NRF_LPCOMP->TASKS_STOP = 1; + NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled; + + // Select analog input (AIN0-7 maps to PSEL 0-7) + NRF_LPCOMP->PSEL = ((uint32_t)ain_channel << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk; + + // Reference: REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) + NRF_LPCOMP->REFSEL = ((uint32_t)refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk; + + // Detect UP events (voltage rises above threshold for battery recovery) + NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up; + + // Enable 50mV hysteresis for noise immunity + NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_Hyst50mV; + + // Clear stale events/interrupts before enabling wake + NRF_LPCOMP->EVENTS_READY = 0; + NRF_LPCOMP->EVENTS_DOWN = 0; + NRF_LPCOMP->EVENTS_UP = 0; + NRF_LPCOMP->EVENTS_CROSS = 0; + + NRF_LPCOMP->INTENCLR = 0xFFFFFFFF; + NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk; + + // Enable LPCOMP + NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled; + NRF_LPCOMP->TASKS_START = 1; + + // Wait for comparator to settle before entering SYSTEMOFF + for (uint8_t i = 0; i < 20 && !NRF_LPCOMP->EVENTS_READY; i++) { + delayMicroseconds(50); + } + + if (refsel == 7) { + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel); + } else if (refsel <= 6) { + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/8 VDD)", + ain_channel, refsel + 1); + } else { + uint8_t ref_num = (uint8_t)((refsel - 8) * 2 + 1); + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/16 VDD)", + ain_channel, ref_num); + } + + // Configure VBUS (USB power) wake alongside LPCOMP + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_usbdetected_enable(1); + } else { + NRF_POWER->EVENTS_USBDETECTED = 0; + NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk; + } + + MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake configured"); +} +#endif + void NRF52BoardDCDC::begin() { NRF52Board::begin(); diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 0d6c0a4316..1f02bace7f 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -5,15 +5,58 @@ #if defined(NRF52_PLATFORM) +#ifdef NRF52_POWER_MANAGEMENT +// Shutdown Reason Codes (stored in GPREGRET before SYSTEMOFF) +#define SHUTDOWN_REASON_NONE 0x00 +#define SHUTDOWN_REASON_LOW_VOLTAGE 0x4C // 'L' - Runtime low voltage threshold +#define SHUTDOWN_REASON_USER 0x55 // 'U' - User requested powerOff() +#define SHUTDOWN_REASON_BOOT_PROTECT 0x42 // 'B' - Boot voltage protection + +// Boards provide this struct with their hardware-specific settings and callbacks. +struct PowerMgtConfig { + // LPCOMP wake configuration (for voltage recovery from SYSTEMOFF) + uint8_t lpcomp_ain_channel; // AIN0-7 for voltage sensing pin + uint8_t lpcomp_refsel; // REFSEL value: 0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16 + + // Boot protection voltage threshold (millivolts) + // Set to 0 to disable boot protection + uint16_t voltage_bootlock; +}; +#endif + class NRF52Board : public mesh::MainBoard { +#ifdef NRF52_POWER_MANAGEMENT + void initPowerMgr(); +#endif + protected: uint8_t startup_reason; +#ifdef NRF52_POWER_MANAGEMENT + uint32_t reset_reason; // RESETREAS register value + uint8_t shutdown_reason; // GPREGRET value (why we entered last SYSTEMOFF) + uint16_t boot_voltage_mv; // Battery voltage at boot (millivolts) + + bool checkBootVoltage(const PowerMgtConfig* config); + void enterSystemOff(uint8_t reason); + void configureVoltageWake(uint8_t ain_channel, uint8_t refsel); + virtual void initiateShutdown(uint8_t reason); +#endif + public: virtual void begin(); virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + +#ifdef NRF52_POWER_MANAGEMENT + bool isExternalPowered() override; + uint16_t getBootVoltage() override { return boot_voltage_mv; } + virtual uint32_t getResetReason() const override { return reset_reason; } + uint8_t getShutdownReason() const override { return shutdown_reason; } + const char* getResetReasonString(uint32_t reason) override; + const char* getShutdownReasonString(uint8_t reason) override; +#endif }; /* diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index 4995e7de1c..2a36bd9039 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -3,6 +3,35 @@ #include #include +#ifdef NRF52_POWER_MANAGEMENT +// Static configuration for power management +// Values come from variant.h defines +const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK +}; + +void T114Board::initiateShutdown(uint8_t reason) { +#if ENV_INCLUDE_GPS == 1 + pinMode(GPS_EN, OUTPUT); + digitalWrite(GPS_EN, LOW); +#endif + digitalWrite(SX126X_POWER_EN, LOW); + + bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT); + pinMode(PIN_BAT_CTL, OUTPUT); + digitalWrite(PIN_BAT_CTL, enable_lpcomp ? HIGH : LOW); + + if (enable_lpcomp) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); +} +#endif // NRF52_POWER_MANAGEMENT + void T114Board::begin() { NRF52Board::begin(); NRF_POWER->DCDCEN = 1; @@ -21,6 +50,11 @@ void T114Board::begin() { #endif pinMode(SX126X_POWER_EN, OUTPUT); +#ifdef NRF52_POWER_MANAGEMENT + // Boot voltage protection check (may not return if voltage too low) + // We need to call this after we configure SX126X_POWER_EN as output but before we pull high + checkBootVoltage(&power_config); +#endif digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up } \ No newline at end of file diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 74e26455c6..cf0f656df8 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -10,6 +10,11 @@ #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range class T114Board : public NRF52BoardOTA { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: T114Board() : NRF52BoardOTA("T114_OTA") {} void begin(); @@ -42,13 +47,13 @@ class T114Board : public NRF52BoardOTA { } void powerOff() override { - #ifdef LED_PIN +#ifdef LED_PIN digitalWrite(LED_PIN, HIGH); - #endif - #if ENV_INCLUDE_GPS == 1 +#endif +#if ENV_INCLUDE_GPS == 1 pinMode(GPS_EN, OUTPUT); digitalWrite(GPS_EN, LOW); - #endif +#endif sd_power_system_off(); } }; diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 7b6f5ceef4..20f5e8fec9 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -11,6 +11,7 @@ build_flags = ${nrf52_base.build_flags} -I variants/heltec_t114 -I src/helpers/ui -D HELTEC_T114 + -D NRF52_POWER_MANAGEMENT -D P_LORA_DIO_1=20 -D P_LORA_NSS=24 -D P_LORA_RESET=25 diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index b3f760bbb0..aa7f40222b 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -30,6 +30,14 @@ #define AREF_VOLTAGE (3.0) +// Power management boot protection threshold (millivolts) +// Set to 0 to disable boot protection +#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) +// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) +// AIN2 = P0.04 = BATTERY_PIN / PIN_VBAT_READ +#define PWRMGT_LPCOMP_AIN 2 +#define PWRMGT_LPCOMP_REFSEL 1 // 2/8 VDD (~3.68-4.04V) + //////////////////////////////////////////////////////////////////////////////// // Number of pins diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 65c54711da..9fb47b432e 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -3,6 +3,28 @@ #include "RAK4631Board.h" +#ifdef NRF52_POWER_MANAGEMENT +// Static configuration for power management +// Values set in variant.h defines +const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK +}; + +void RAK4631Board::initiateShutdown(uint8_t reason) { + // Disable LoRa module power before shutdown + digitalWrite(SX126X_POWER_EN, LOW); + + if (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); +} +#endif // NRF52_POWER_MANAGEMENT + void RAK4631Board::begin() { NRF52BoardDCDC::begin(); pinMode(PIN_VBAT_READ, INPUT); @@ -21,6 +43,11 @@ void RAK4631Board::begin() { Wire.begin(); pinMode(SX126X_POWER_EN, OUTPUT); +#ifdef NRF52_POWER_MANAGEMENT + // Boot voltage protection check (may not return if voltage too low) + // We need to call this after we configure SX126X_POWER_EN as output but before we pull high + checkBootVoltage(&power_config); +#endif digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up } \ No newline at end of file diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index a181256b0d..53a2a7971b 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -30,6 +30,11 @@ #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {} void begin(); diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 7293b4d49c..9a9ab2dd30 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -7,6 +7,7 @@ build_flags = ${nrf52_base.build_flags} -I variants/rak4631 -D RAK_4631 -D RAK_BOARD + -D NRF52_POWER_MANAGEMENT -D PIN_BOARD_SCL=14 -D PIN_BOARD_SDA=13 -D PIN_GPS_TX=PIN_SERIAL1_RX diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index e83d1339ed..b18335f8da 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -104,6 +104,14 @@ extern "C" static const uint8_t A7 = PIN_A7; #define ADC_RESOLUTION 14 +// Power management boot protection threshold (millivolts) +// Set to 0 to disable boot protection +#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) +// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) +// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ +#define PWRMGT_LPCOMP_AIN 3 +#define PWRMGT_LPCOMP_REFSEL 4 // 5/8 VDD (~3.13-3.44V) + // Other pins #define PIN_AREF (2) #define PIN_NFC1 (9) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index b7b60dc63d..42ee6a87fe 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -5,12 +5,40 @@ #include "XiaoNrf52Board.h" +#ifdef NRF52_POWER_MANAGEMENT +// Static configuration for power management +// Values set in variant.h defines +const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK +}; + +void XiaoNrf52Board::initiateShutdown(uint8_t reason) { + bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT); + + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, enable_lpcomp ? LOW : HIGH); + + if (enable_lpcomp) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); +} +#endif // NRF52_POWER_MANAGEMENT + void XiaoNrf52Board::begin() { NRF52BoardDCDC::begin(); + // Configure battery voltage ADC pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); - digitalWrite(VBAT_ENABLE, HIGH); + digitalWrite(VBAT_ENABLE, LOW); // Enable VBAT divider for reading + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(50); // Allow ADC to settle #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); @@ -27,9 +55,20 @@ void XiaoNrf52Board::begin() { digitalWrite(P_LORA_TX_LED, HIGH); #endif - // pinMode(SX126X_POWER_EN, OUTPUT); - // digitalWrite(SX126X_POWER_EN, HIGH); - delay(10); // give sx1262 some time to power up +#ifdef NRF52_POWER_MANAGEMENT + // Boot voltage protection check (may not return if voltage too low) + checkBootVoltage(&power_config); +#endif + + delay(10); // Give sx1262 some time to power up +} + +uint16_t XiaoNrf52Board::getBattMilliVolts() { + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + // VBAT_ENABLE must be LOW to read battery voltage + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; } #endif \ No newline at end of file diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 1c46dfeee6..db9ec380de 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -6,7 +6,12 @@ #ifdef XIAO_NRF52 -class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { +protected: +#if NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); @@ -20,21 +25,7 @@ class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { } #endif - uint16_t getBattMilliVolts() override { - // Please read befor going further ;) - // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging - - // We can't drive VBAT_ENABLE to HIGH as long - // as we don't know wether we are charging or not ... - // this is a 3mA loss (4/1500) - digitalWrite(VBAT_ENABLE, LOW); - int adcvalue = 0; - analogReadResolution(12); - analogReference(AR_INTERNAL_3_0); - delay(10); - adcvalue = analogRead(PIN_VBAT); - return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; - } + uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override { return "Seeed Xiao-nrf52"; diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index edbf6275ed..6e96018bc3 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -9,6 +9,7 @@ build_flags = ${nrf52_base.build_flags} -I variants/xiao_nrf52 -UENV_INCLUDE_GPS -D NRF52_PLATFORM + -D NRF52_POWER_MANAGEMENT -D XIAO_NRF52 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index 3f4d7afeb1..25619b9e59 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -75,6 +75,21 @@ static const uint8_t D10 = 10; #define AREF_VOLTAGE (3.0) #define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge +// Power management boot protection threshold (millivolts) +// Set to 0 to disable boot protection +#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage + +// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) +#define PWRMGT_LPCOMP_AIN 7 // AIN7 = P0.31 = PIN_VBAT +// IMPORTANT: The XIAO exposes battery via a resistor divider (ADC_MULTIPLIER = 3.0). +// LPCOMP measures the divided voltage, not the battery voltage directly. +// Vpin = VDD * (REFSEL fraction), and VBAT ≈ Vpin * ADC_MULTIPLIER. +// +// Using 3/8 VDD gives a wake threshold above the boot protection point: +// - If VDD ≈ 3.0V: VBAT ≈ (3.0 * 3/8) * 3 ≈ 3375mV +// - If VDD ≈ 3.3V: VBAT ≈ (3.3 * 3/8) * 3 ≈ 3712mV +#define PWRMGT_LPCOMP_REFSEL 2 // 3/8 VDD (~3.38-3.71V) + static const uint8_t A0 = PIN_A0; static const uint8_t A1 = PIN_A1; static const uint8_t A2 = PIN_A2; From 9dd52bd0cccb803d2dec9d6d276d5add53bb0b84 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 23 Jan 2026 23:40:24 +1100 Subject: [PATCH 272/409] build fix for room server with MESH_DEBUG=1 --- examples/simple_room_server/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 60dd18407c..d18a802e84 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -815,7 +815,7 @@ void MyMesh::loop() { if (c->extra.room.pending_ack && millisHasNowPassed(c->extra.room.ack_timeout)) { c->extra.room.push_failures++; c->extra.room.pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry) - MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures); + MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->extra.room.push_failures); } } // check next Round-Robin client, and sync next new post From e7c72c5c6ad0ca5b225e40b260c7f9bd1114d08a Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 23 Jan 2026 22:26:24 +0100 Subject: [PATCH 273/409] initial port of rak3112 --- variants/rak3112/RAK3112Board.h | 97 ++++++++++++++ variants/rak3112/platformio.ini | 223 ++++++++++++++++++++++++++++++++ variants/rak3112/target.cpp | 60 +++++++++ variants/rak3112/target.h | 30 +++++ 4 files changed, 410 insertions(+) create mode 100644 variants/rak3112/RAK3112Board.h create mode 100644 variants/rak3112/platformio.ini create mode 100644 variants/rak3112/target.cpp create mode 100644 variants/rak3112/target.h diff --git a/variants/rak3112/RAK3112Board.h b/variants/rak3112/RAK3112Board.h new file mode 100644 index 0000000000..8dddbefd46 --- /dev/null +++ b/variants/rak3112/RAK3112Board.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include + +// built-ins +#ifndef PIN_VBAT_READ + #define PIN_VBAT_READ 1 +#endif +#ifndef PIN_ADC_CTRL + #define PIN_ADC_CTRL 36 +#endif +#define PIN_ADC_CTRL_ACTIVE LOW +#define PIN_ADC_CTRL_INACTIVE HIGH + +#include + +class RAK3112Board : public ESP32Board { +private: + bool adc_active_state; + +public: + RefCountedDigitalPin periph_power; + + RAK3112Board() : periph_power(PIN_VEXT_EN) { } + + void begin() { + ESP32Board::begin(); + + // Auto-detect correct ADC_CTRL pin polarity (different for boards >3.2) + pinMode(PIN_ADC_CTRL, INPUT); + adc_active_state = !digitalRead(PIN_ADC_CTRL); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive + + periph_power.begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void powerOff() override { + enterDeepSleep(0); + } + + uint16_t getBattMilliVolts() override { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, adc_active_state); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, !adc_active_state); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* getManufacturerName() const override { + return "RAK 3112"; + } +}; diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini new file mode 100644 index 0000000000..29ebdff27c --- /dev/null +++ b/variants/rak3112/platformio.ini @@ -0,0 +1,223 @@ +[rak3112] +extends = esp32_base +board = esp32-s3-devkitc-1 +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/rak3112 + -D RAK_3112=1 + -D ESP32_CPU_FREQ=80 + -D P_LORA_DIO_1=47 + -D P_LORA_NSS=7 + -D P_LORA_RESET=8 + -D P_LORA_BUSY=48 + -D P_LORA_SCLK=5 + -D P_LORA_MISO=3 + -D P_LORA_MOSI=6 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=46 + -D PIN_BOARD_SDA=9 + -D PIN_BOARD_SCL=40 + -D PIN_USER_BTN=-1 + -D PIN_VEXT_EN=14 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_GPS_RX=43 + -D PIN_GPS_TX=44 +; -D PIN_GPS_EN=26 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/rak3112> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + +[env:RAK3112_repeater] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK3112 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 = ${rak3112.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:RAK3112_repeater_bridge_rs232] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=5 + -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} + +[env:RAK3112_repeater_bridge_espnow] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} + +[env:RAK3112_room_server] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK3112 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} + +[env:RAK3112_terminal_chat] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${rak3112.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK3112_companion_radio_usb] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3112.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK3112_companion_radio_ble] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3112.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK3112_companion_radio_wifi] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -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 = ${rak3112.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3112.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK3112_sensor] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D ADVERT_NAME='"RAK3112 v3 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + + + +<../examples/simple_sensor> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/rak3112/target.cpp b/variants/rak3112/target.cpp new file mode 100644 index 0000000000..634573b81e --- /dev/null +++ b/variants/rak3112/target.cpp @@ -0,0 +1,60 @@ +#include +#include "target.h" + +RAK3112Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/rak3112/target.h b/variants/rak3112/target.h new file mode 100644 index 0000000000..eae90900df --- /dev/null +++ b/variants/rak3112/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern RAK3112Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From 266f6ee8560083de8805a87228691edb9c2f159a Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 23 Jan 2026 22:41:47 +0100 Subject: [PATCH 274/409] fixed battery measurement --- variants/rak3112/RAK3112Board.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/variants/rak3112/RAK3112Board.h b/variants/rak3112/RAK3112Board.h index 8dddbefd46..8ba3197cf6 100644 --- a/variants/rak3112/RAK3112Board.h +++ b/variants/rak3112/RAK3112Board.h @@ -13,6 +13,8 @@ #endif #define PIN_ADC_CTRL_ACTIVE LOW #define PIN_ADC_CTRL_INACTIVE HIGH +#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) +#define BATTERY_SAMPLES 8 #include @@ -77,18 +79,15 @@ class RAK3112Board : public ESP32Board { } uint16_t getBattMilliVolts() override { - analogReadResolution(10); - digitalWrite(PIN_ADC_CTRL, adc_active_state); + analogReadResolution(12); uint32_t raw = 0; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < BATTERY_SAMPLES; i++) { raw += analogRead(PIN_VBAT_READ); } - raw = raw / 8; + raw = raw / BATTERY_SAMPLES; - digitalWrite(PIN_ADC_CTRL, !adc_active_state); - - return (5.42 * (3.3 / 1024.0) * raw) * 1000; + return (ADC_MULTIPLIER * raw) / 4096; } const char* getManufacturerName() const override { From f46f0d0ed137358752e6a9acaa14a30a0009793d Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 24 Jan 2026 22:08:05 +1100 Subject: [PATCH 275/409] * WIO tracker l1: BLE companion. default node name now MAC address --- variants/wio-tracker-l1/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 31c6bcb075..75651d6957 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -95,6 +95,7 @@ build_flags = ${WioTrackerL1.build_flags} -D UI_HAS_JOYSTICK=1 -D PIN_BUZZER=12 -D QSPIFLASH=1 + -D ADVERT_NAME='"@@MAC"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${WioTrackerL1.build_src_filter} From 6336bd5b72b8f72d21c3d5f4d0e5d0fc09e9838a Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 25 Jan 2026 01:31:53 +1100 Subject: [PATCH 276/409] refactor ClientACL and CommonCLI, add ClientACL::clear() --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_sensor/SensorMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/ClientACL.cpp | 14 +++++++++++++- src/helpers/ClientACL.h | 2 ++ src/helpers/CommonCLI.h | 6 ++++-- 9 files changed, 25 insertions(+), 9 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d926148d62..78e6abcb9b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -744,7 +744,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), + _cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), discover_limiter(4, 120), // max 4 every 2 minutes anon_limiter(4, 180) // max 4 every 3 minutes #if defined(WITH_RS232_BRIDGE) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f930ee7eb3..282fc8c2ba 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -86,11 +86,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { unsigned long next_local_advert, next_flood_advert; bool _logging; NodePrefs _prefs; + ClientACL acl; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; uint8_t reply_path[MAX_PATH_SIZE]; int8_t reply_path_len; - ClientACL acl; TransportKeyStore key_store; RegionMap region_map, temp_map; RegionEntry* load_stack[8]; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 60dd18407c..3e935fe6de 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -587,7 +587,7 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) { MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { + _cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { last_millis = 0; uptime_millis = 0; next_local_advert = next_flood_advert = 0; diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index e7f1fee83d..f6adf01e09 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -94,8 +94,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { unsigned long next_local_advert, next_flood_advert; bool _logging; NodePrefs _prefs; - CommonCLI _cli; ClientACL acl; + CommonCLI _cli; unsigned long dirty_contacts_expiry; uint8_t reply_data[MAX_PACKET_PAYLOAD]; unsigned long next_push; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 4995c55fce..53d8326f06 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -695,7 +695,7 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index c320eb447c..6046a1df7a 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -133,9 +133,9 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { FILESYSTEM* _fs; unsigned long next_local_advert, next_flood_advert; NodePrefs _prefs; + ClientACL acl; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; - ClientACL acl; unsigned long dirty_contacts_expiry; CayenneLPP telemetry; uint32_t last_read_time; diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 4ea19fd297..ffa717b2af 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -12,6 +12,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { } void ClientACL::load(FILESYSTEM* _fs) { + _fs = fs; num_clients = 0; if (_fs->exists("/s_contacts")) { #if defined(RP2040_PLATFORM) @@ -50,7 +51,8 @@ void ClientACL::load(FILESYSTEM* _fs) { } } -void ClientACL::save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)) { +void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) { + _fs = fs; File file = openWrite(_fs, "/s_contacts"); if (file) { uint8_t unused[2]; @@ -74,6 +76,16 @@ void ClientACL::save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)) { } } +bool ClientACL::clear() { + if (!_fs) return false; // no filesystem, nothing to clear + if (_fs->exists("/s_contacts")) { + _fs->remove("/s_contacts"); + } + memset(clients, 0, sizeof(clients)); + num_clients = 0; + return true; +} + ClientInfo* ClientACL::getClient(const uint8_t* pubkey, int key_len) { for (int i = 0; i < num_clients; i++) { if (memcmp(pubkey, clients[i].id.pub_key, key_len) == 0) return &clients[i]; // already known diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index 1b650edd28..34a0dd85ad 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -36,6 +36,7 @@ struct ClientInfo { #endif class ClientACL { + FILESYSTEM* _fs; ClientInfo clients[MAX_CLIENTS]; int num_clients; @@ -46,6 +47,7 @@ class ClientACL { } void load(FILESYSTEM* _fs); void save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)=NULL); + bool clear(); ClientInfo* getClient(const uint8_t* pubkey, int key_len); ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 3b1d05f96a..b053010219 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -3,6 +3,7 @@ #include "Mesh.h" #include #include +#include #if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) #define WITH_BRIDGE @@ -94,6 +95,7 @@ class CommonCLI { CommonCLICallbacks* _callbacks; mesh::MainBoard* _board; SensorManager* _sensors; + ClientACL* _acl; char tmp[PRV_KEY_SIZE*2 + 4]; mesh::RTCClock* getRTCClock() { return _rtc; } @@ -101,8 +103,8 @@ class CommonCLI { void loadPrefsInt(FILESYSTEM* _fs, const char* filename); public: - CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, NodePrefs* prefs, CommonCLICallbacks* callbacks) - : _board(&board), _rtc(&rtc), _sensors(&sensors), _prefs(prefs), _callbacks(callbacks) { } + CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks) + : _board(&board), _rtc(&rtc), _sensors(&sensors), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { } void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); From 988287bfd7c1a1e8c38e31ca37805ae1829d0d67 Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 25 Jan 2026 01:32:44 +1100 Subject: [PATCH 277/409] recalc ClientACL shared_secrets at startup --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 2 +- src/helpers/ClientACL.cpp | 5 +++-- src/helpers/ClientACL.h | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 78e6abcb9b..59c21ae78c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -808,7 +808,7 @@ void MyMesh::begin(FILESYSTEM *fs) { _fs = fs; // load persisted prefs _cli.loadPrefs(_fs); - acl.load(_fs); + acl.load(_fs, self_id); // TODO: key_store.begin(); region_map.load(_fs); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 3e935fe6de..2f929dd565 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -637,7 +637,7 @@ void MyMesh::begin(FILESYSTEM *fs) { // load persisted prefs _cli.loadPrefs(_fs); - acl.load(_fs); + acl.load(_fs, self_id); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 53d8326f06..c384a76154 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -736,7 +736,7 @@ void SensorMesh::begin(FILESYSTEM* fs) { // load persisted prefs _cli.loadPrefs(_fs); - acl.load(_fs); + acl.load(_fs, self_id); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index ffa717b2af..55b70ca55c 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -11,7 +11,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { #endif } -void ClientACL::load(FILESYSTEM* _fs) { +void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) { _fs = fs; num_clients = 0; if (_fs->exists("/s_contacts")) { @@ -35,11 +35,12 @@ void ClientACL::load(FILESYSTEM* _fs) { success = success && (file.read(unused, 2) == 2); success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); success = success && (file.read(c.out_path, 64) == 64); - success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); + success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); // will be recalculated below if (!success) break; // EOF c.id = mesh::Identity(pub_key); + self_id.calcSharedSecret(c.shared_secret, pub_key); // recalculate shared secrets in case our private key changed if (num_clients < MAX_CLIENTS) { clients[num_clients++] = c; } else { diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index 34a0dd85ad..dfbc3fce1f 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -45,7 +45,7 @@ class ClientACL { memset(clients, 0, sizeof(clients)); num_clients = 0; } - void load(FILESYSTEM* _fs); + void load(FILESYSTEM* _fs, const mesh::LocalIdentity& self_id); void save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)=NULL); bool clear(); From 96ef5e5efe48e7115e56ffca1707d3e07eb93901 Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 25 Jan 2026 00:51:48 +1100 Subject: [PATCH 278/409] allow set prv.key from remote, validate new prv.key --- examples/companion_radio/MyMesh.cpp | 22 +++++++------ examples/simple_repeater/MyMesh.cpp | 3 +- examples/simple_room_server/MyMesh.cpp | 3 +- examples/simple_sensor/SensorMesh.cpp | 3 +- src/Identity.cpp | 44 ++++++++++++++++++++++++++ src/Identity.h | 7 ++++ src/helpers/CommonCLI.cpp | 11 ++++--- 7 files changed, 73 insertions(+), 20 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9de91e4585..2dad7866af 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1294,16 +1294,20 @@ void MyMesh::handleCmdFrame(size_t len) { #endif } else if (cmd_frame[0] == CMD_IMPORT_PRIVATE_KEY && len >= 65) { #if ENABLE_PRIVATE_KEY_IMPORT - mesh::LocalIdentity identity; - identity.readFrom(&cmd_frame[1], 64); - if (_store->saveMainIdentity(identity)) { - self_id = identity; - writeOKFrame(); - // re-load contacts, to invalidate ecdh shared_secrets - resetContacts(); - _store->loadContacts(this); + if (!mesh::LocalIdentity::validatePrivateKey(&cmd_frame[1])) { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid key } else { - writeErrFrame(ERR_CODE_FILE_IO_ERROR); + mesh::LocalIdentity identity; + identity.readFrom(&cmd_frame[1], 64); + if (_store->saveMainIdentity(identity)) { + self_id = identity; + writeOKFrame(); + // re-load contacts, to invalidate ecdh shared_secrets + resetContacts(); + _store->loadContacts(this); + } else { + writeErrFrame(ERR_CODE_FILE_IO_ERROR); + } } #else writeDisabledFrame(); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 59c21ae78c..b30072b85c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -968,7 +968,6 @@ void MyMesh::formatPacketStatsReply(char *reply) { } void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { - self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -978,7 +977,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void MyMesh::clearStats() { diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 2f929dd565..9d93eade32 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -720,7 +720,6 @@ void MyMesh::setTxPower(uint8_t power_dbm) { } void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { - self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -730,7 +729,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void MyMesh::clearStats() { diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index c384a76154..201532b959 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -765,7 +765,6 @@ bool SensorMesh::formatFileSystem() { } void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) { - self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -775,7 +774,7 @@ void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { diff --git a/src/Identity.cpp b/src/Identity.cpp index 8329892830..ea546274da 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -48,6 +48,50 @@ LocalIdentity::LocalIdentity(RNG* rng) { ed25519_create_keypair(pub_key, prv_key, seed); } +bool LocalIdentity::validatePrivateKey(const uint8_t prv[64]) { + uint8_t pub[32]; + ed25519_derive_pub(pub, prv); // derive public key from given private key + + // disallow 00 or FF prefixed public keys + if (pub[0] == 0x00 || pub[0] == 0xFF) return false; + + // known good test client keypair + const uint8_t test_client_prv[64] = { + 0x70, 0x65, 0xe1, 0x8f, 0xd9, 0xfa, 0xbb, 0x70, + 0xc1, 0xed, 0x90, 0xdc, 0xa1, 0x99, 0x07, 0xde, + 0x69, 0x8c, 0x88, 0xb7, 0x09, 0xea, 0x14, 0x6e, + 0xaf, 0xd9, 0x3d, 0x9b, 0x83, 0x0c, 0x7b, 0x60, + 0xc4, 0x68, 0x11, 0x93, 0xc7, 0x9b, 0xbc, 0x39, + 0x94, 0x5b, 0xa8, 0x06, 0x41, 0x04, 0xbb, 0x61, + 0x8f, 0x8f, 0xd7, 0xa8, 0x4a, 0x0a, 0xf6, 0xf5, + 0x70, 0x33, 0xd6, 0xe8, 0xdd, 0xcd, 0x64, 0x71 + }; + const uint8_t test_client_pub[32] = { + 0x1e, 0xc7, 0x71, 0x75, 0xb0, 0x91, 0x8e, 0xd2, + 0x06, 0xf9, 0xae, 0x04, 0xec, 0x13, 0x6d, 0x6d, + 0x5d, 0x43, 0x15, 0xbb, 0x26, 0x30, 0x54, 0x27, + 0xf6, 0x45, 0xb4, 0x92, 0xe9, 0x35, 0x0c, 0x10 + }; + + uint8_t ss1[32], ss2[32]; + + // shared secret we calculte from test client pubkey and given private key + ed25519_key_exchange(ss1, test_client_pub, prv); + + // shared secret they calculate from our derived public key and test client private key + ed25519_key_exchange(ss2, pub, test_client_prv); + + // check that both shared secrets match + if (memcmp(ss1, ss2, 32) != 0) return false; + + // reject all-zero shared secret + for (int i = 0; i < 32; i++) { + if (ss1[i] != 0) return true; + } + + return false; +} + bool LocalIdentity::readFrom(Stream& s) { bool success = (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE); success = success && (s.readBytes(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE); diff --git a/src/Identity.h b/src/Identity.h index 60e8783b9b..c3ffcd75e3 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -76,6 +76,13 @@ class LocalIdentity : public Identity { */ void calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) const; + /** + * \brief Validates that a given private key can be used for ECDH / shared-secret operations. + * \param prv IN - the private key to validate (must be PRV_KEY_SIZE bytes) + * \returns true, if the private key is valid for login. + */ + static bool validatePrivateKey(const uint8_t prv[64]); + bool readFrom(Stream& s); bool writeTo(Stream& s) const; void printTo(Stream& s) const; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 2fc93006b0..878561c51b 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -416,17 +416,18 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password)); savePrefs(); strcpy(reply, "OK"); - } else if (sender_timestamp == 0 && - memcmp(config, "prv.key ", 8) == 0) { // from serial command line only + } else if (memcmp(config, "prv.key ", 8) == 0) { uint8_t prv_key[PRV_KEY_SIZE]; bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]); - if (success) { + // only allow rekey if key is valid + if (success && mesh::LocalIdentity::validatePrivateKey(prv_key)) { mesh::LocalIdentity new_id; new_id.readFrom(prv_key, PRV_KEY_SIZE); _callbacks->saveIdentity(new_id); - strcpy(reply, "OK"); + strcpy(reply, "OK, reboot to apply! New pubkey: "); + mesh::Utils::toHex(&reply[33], new_id.pub_key, PUB_KEY_SIZE); } else { - strcpy(reply, "Error, invalid key"); + strcpy(reply, "Error, bad key"); } } else if (memcmp(config, "name ", 5) == 0) { if (isValidName(&config[5])) { From c16bcd2fe38e2b309961a917f9d47db952f737ef Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 24 Jan 2026 20:06:29 -0800 Subject: [PATCH 279/409] Expose a counter to track RadioLib receive errors This change counts when readData returns an err code other than RADIOLIB_ERR_NONE. In most cases this is going to be a CRC error. This counter is exposed in the `stats-packets` command, and in the repeater stats payload (4 additional bytes to the payload, which is now 56 bytes with this change. My incompetent robot claims the total payload size is 96 bytes (unverified but probably close). --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_repeater/MyMesh.h | 1 + src/helpers/StatsFormatHelper.h | 5 +++-- src/helpers/radiolib/RadioLibWrappers.cpp | 1 + src/helpers/radiolib/RadioLibWrappers.h | 5 +++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d926148d62..6987530433 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -226,7 +226,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); stats.total_rx_air_time_secs = getReceiveAirTime() / 1000; - + stats.n_recv_errors = radio_driver.getPacketsRecvErrors(); memcpy(&reply_data[4], &stats, sizeof(stats)); return 4 + sizeof(stats); // reply_len diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f930ee7eb3..ad1c7febee 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -54,6 +54,7 @@ struct RepeaterStats { int16_t last_snr; // x 4 uint16_t n_direct_dups, n_flood_dups; uint32_t total_rx_air_time_secs; + uint32_t n_recv_errors; }; #ifndef MAX_CLIENTS diff --git a/src/helpers/StatsFormatHelper.h b/src/helpers/StatsFormatHelper.h index d0107f3b30..5aa01da973 100644 --- a/src/helpers/StatsFormatHelper.h +++ b/src/helpers/StatsFormatHelper.h @@ -42,13 +42,14 @@ class StatsFormatHelper { uint32_t n_recv_flood, uint32_t n_recv_direct) { sprintf(reply, - "{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u}", + "{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u,\"recv_errors\":%u}", driver.getPacketsRecv(), driver.getPacketsSent(), n_sent_flood, n_sent_direct, n_recv_flood, - n_recv_direct + n_recv_direct, + driver.getPacketsRecvErrors() ); } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index e340782112..cf3e1266b9 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -105,6 +105,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) { if (err != RADIOLIB_ERR_NONE) { MESH_DEBUG_PRINTLN("RadioLibWrapper: error: readData(%d)", err); len = 0; + n_recv_errors++; } else { // Serial.print(" readData() -> "); Serial.println(len); n_recv++; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 3c26d37272..9ac1bbaeb3 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -7,7 +7,7 @@ class RadioLibWrapper : public mesh::Radio { protected: PhysicalLayer* _radio; mesh::MainBoard* _board; - uint32_t n_recv, n_sent; + uint32_t n_recv, n_sent, n_recv_errors; int16_t _noise_floor, _threshold; uint16_t _num_floor_samples; int32_t _floor_sample_sum; @@ -45,8 +45,9 @@ class RadioLibWrapper : public mesh::Radio { void loop() override; uint32_t getPacketsRecv() const { return n_recv; } + uint32_t getPacketsRecvErrors() const { return n_recv_errors; } uint32_t getPacketsSent() const { return n_sent; } - void resetStats() { n_recv = n_sent = 0; } + void resetStats() { n_recv = n_sent = n_recv_errors = 0; } virtual float getLastRSSI() const override; virtual float getLastSNR() const override; From 7ae164217c54eb8ab2ad67cc71e1f63ee6c7b960 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 25 Jan 2026 18:35:55 +1100 Subject: [PATCH 280/409] * region names now don't need '#' prefix. (SHA still adds a '#' for back compat) --- src/helpers/RegionMap.cpp | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index fbc5f01729..35692762ba 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -11,7 +11,11 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { bool RegionMap::is_name_char(uint8_t c) { // accept all alpha-num or accented characters, but exclude most punctuation chars - return c == '-' || c == '#' || (c >= '0' && c <= '9') || c >= 'A'; + return c == '-' || c == '$' || c == '#' || (c >= '0' && c <= '9') || c >= 'A'; +} + +static const char* skip_hash(const char* name) { + return *name == '#' ? name + 1 : name; } static File openWrite(FILESYSTEM* _fs, const char* filename) { @@ -127,11 +131,17 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param) TransportKey keys[4]; int num; - if (region->name[0] == '#') { // auto hashtag region + if (region->name[0] == '$') { // private region + num = _store->loadKeysFor(region->id, keys, 4); + } else if (region->name[0] == '#') { // auto hashtag region _store->getAutoKeyFor(region->id, region->name, keys[0]); num = 1; - } else { - num = _store->loadKeysFor(region->id, keys, 4); + } else { // new: implicit auto hashtag region + char tmp[sizeof(region->name)]; + tmp[0] = '#'; + strcpy(&tmp[1], region->name); + _store->getAutoKeyFor(region->id, tmp, keys[0]); + num = 1; } for (int j = 0; j < num; j++) { uint16_t code = keys[j].calcTransportCode(packet); @@ -147,9 +157,10 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { RegionEntry* RegionMap::findByName(const char* name) { if (strcmp(name, "*") == 0) return &wildcard; + if (*name == '#') { name++; } // ignore the '#' when matching by name for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if (strcmp(name, region->name) == 0) return region; + if (strcmp(name, skip_hash(region->name)) == 0) return region; } return NULL; // not found } @@ -157,11 +168,12 @@ RegionEntry* RegionMap::findByName(const char* name) { RegionEntry* RegionMap::findByNamePrefix(const char* prefix) { if (strcmp(prefix, "*") == 0) return &wildcard; + if (*prefix == '#') { prefix++; } // ignore the '#' when matching by name RegionEntry* partial = NULL; for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if (strcmp(prefix, region->name) == 0) return region; // is a complete match, preference this one - if (memcmp(prefix, region->name, strlen(prefix)) == 0) { + if (strcmp(prefix, skip_hash(region->name)) == 0) return region; // is a complete match, preference this one + if (memcmp(prefix, skip_hash(region->name), strlen(prefix)) == 0) { partial = region; } } @@ -220,9 +232,9 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& } if (parent->flags & REGION_DENY_FLOOD) { - out.printf("%s%s\n", parent->name, parent->id == home_id ? "^" : ""); + out.printf("%s%s\n", skip_hash(parent->name), parent->id == home_id ? "^" : ""); } else { - out.printf("%s%s F\n", parent->name, parent->id == home_id ? "^" : ""); + out.printf("%s%s F\n", skip_hash(parent->name), parent->id == home_id ? "^" : ""); } for (int i = 0; i < num_regions; i++) { @@ -247,9 +259,10 @@ int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) { for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param) - int len = strlen(region->name); + const char* name = skip_hash(region->name); + int len = strlen(name); if ((dp - dest) + len + 2 < max_len) { // only append if name will fit - memcpy(dp, region->name, len); + memcpy(dp, name, len); dp += len; *dp++ = ','; } From c7ac16f0e371b92c111f15124a271a5cba1ec7b5 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Mon, 26 Jan 2026 13:48:15 +0800 Subject: [PATCH 281/409] Add v4-tft code. --- src/helpers/ui/SSD1306Display.cpp | 14 +- src/helpers/ui/SSD1306Display.h | 9 +- src/helpers/ui/ST7789LCDDisplay.cpp | 5 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4/HeltecV4Board.cpp | 6 +- variants/heltec_v4/platformio.ini | 266 +++++++++++++++++++++++---- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 6 +- 8 files changed, 267 insertions(+), 45 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index c9da0cf8d5..4e7fd10ad0 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,6 +7,10 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } #ifdef DISPLAY_ROTATION display.setRotation(DISPLAY_ROTATION); #endif @@ -15,12 +19,18 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { display.ssd1306_command(SSD1306_DISPLAYON); - _isOn = true; + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); - _isOn = false; + if (_isOn) { + if (_peripher_power) _peripher_power->release(); + _isOn = false; + } } void SSD1306Display::clear() { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index 1a3a9602bb..d843da85b2 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -5,6 +5,7 @@ #include #define SSD1306_NO_SPLASH #include +#include #ifndef PIN_OLED_RESET #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) @@ -18,10 +19,16 @@ class SSD1306Display : public DisplayDriver { Adafruit_SSD1306 display; bool _isOn; uint8_t _color; + RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(128, 64, &Wire, PIN_OLED_RESET), + _peripher_power(peripher_power) + { + _isOn = false; + } bool begin(); bool isOn() override { return _isOn; } diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 97d82f4292..9fd0b23dea 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -28,11 +28,14 @@ bool ST7789LCDDisplay::begin() { digitalWrite(PIN_TFT_LEDA_CTL, HIGH); } if (PIN_TFT_RST != -1) { + pinMode(PIN_TFT_RST, OUTPUT); + digitalWrite(PIN_TFT_RST, LOW); + delay(10); digitalWrite(PIN_TFT_RST, HIGH); } // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); #endif diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index a807714855..5b960ca198 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include class ST7789LCDDisplay : public DisplayDriver { - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ class ST7789LCDDisplay : public DisplayDriver { { _isOn = false; } -#elif LILYGO_TDECK +#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index f143db36f1..92f9343767 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -86,5 +86,9 @@ void HeltecV4Board::begin() { } const char* HeltecV4Board::getManufacturerName() const { - return "Heltec V4"; + #ifdef HELTEC_LORA_V4_TFT + return "Heltec V4 TFT"; + #else + return "Heltec V4 OLED"; + #endif } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ecfd7889b1..ba75900940 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,11 +20,9 @@ build_flags = -D P_LORA_PA_POWER=7 ;power en -D P_LORA_PA_EN=2 -D P_LORA_PA_TX_EN=46 ;enable tx - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VEXT_EN_ACTIVE=LOW -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true @@ -47,10 +45,44 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[env:heltec_v4_repeater] +[heltec_v4_oled] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_OLED + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D ENV_PIN_SDA=4 + -D ENV_PIN_SCL=3 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = ${Heltec_lora32_v4.lib_deps} + +[heltec_v4_tft] extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_TFT + -D PIN_BOARD_SDA=4 + -D PIN_BOARD_SCL=3 + -D DISPLAY_SCALE_X=2.5 + -D DISPLAY_SCALE_Y=3.75 + -D PIN_TFT_RST=18 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=21 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=15 + -D PIN_TFT_DC=16 + -D PIN_TFT_SCL=17 + -D PIN_TFT_SDA=33 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = + ${Heltec_lora32_v4.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:heltec_v4_repeater] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Repeater"' -D ADVERT_LAT=0.0 @@ -59,18 +91,18 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 [env:heltec_v4_repeater_bridge_espnow] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 @@ -81,18 +113,18 @@ build_flags = ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_room_server] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Room"' -D ADVERT_LAT=0.0 @@ -101,50 +133,50 @@ build_flags = -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_room_server> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_terminal_chat] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_usb] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_ble] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -155,20 +187,20 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_wifi] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -176,24 +208,23 @@ 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} +build_src_filter = ${heltec_v4_oled.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_sensor] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} - -D ADVERT_NAME='"Heltec v3 Sensor"' + ${heltec_v4_oled.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -202,9 +233,172 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} + +<../examples/simple_sensor> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} + ${esp32_ota.lib_deps} + + +[env:heltec_v4_tft_repeater] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + + +[env:heltec_v4_tft_repeater_bridge_espnow] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_room_server] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_terminal_chat] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_usb] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_ble] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=ST7789LCDDisplay + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_wifi] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_sensor] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + -D DISPLAY_CLASS=ST7789LCDDisplay +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + + + +<../examples/simple_sensor> +lib_deps = + ${heltec_v4_tft.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 015c3a8e76..0d2bd4976c 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display; + DISPLAY_CLASS display(&(board.periph_power)); MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index a153b2af35..00d2adab6d 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -9,7 +9,11 @@ #include #include #ifdef DISPLAY_CLASS - #include +#ifdef HELTEC_LORA_V4_OLED + #include +#elif defined(HELTEC_LORA_V4_TFT) + #include +#endif #include #endif From ed589f9620cf7a97c62ae64e9e42e48aca1375aa Mon Sep 17 00:00:00 2001 From: liamcottle Date: Mon, 26 Jan 2026 22:20:36 +1300 Subject: [PATCH 282/409] boot adverts are now zero hop instead of flood --- examples/simple_repeater/MyMesh.cpp | 8 ++++++-- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_repeater/main.cpp | 4 ++-- examples/simple_room_server/MyMesh.cpp | 8 ++++++-- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_room_server/main.cpp | 4 ++-- examples/simple_sensor/SensorMesh.cpp | 8 ++++++-- examples/simple_sensor/SensorMesh.h | 2 +- examples/simple_sensor/main.cpp | 4 ++-- src/helpers/CommonCLI.cpp | 3 ++- src/helpers/CommonCLI.h | 2 +- 11 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b838bdcbe9..2d905511ba 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -854,10 +854,14 @@ bool MyMesh::formatFileSystem() { #endif } -void MyMesh::sendSelfAdvertisement(int delay_millis) { +void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { - sendFlood(pkt, delay_millis); + if (flood) { + sendFlood(pkt, delay_millis); + } else { + sendZeroHop(pkt, delay_millis); + } } else { MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 38eb658d17..60d229021c 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -187,7 +187,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; bool formatFileSystem() override; - void sendSelfAdvertisement(int delay_millis) override; + void sendSelfAdvertisement(int delay_millis, bool flood) override; void updateAdvertTimer() override; void updateFloodAdvertTimer() override; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 8c745613e7..d7e10fe28e 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -87,8 +87,8 @@ void setup() { ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif - // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvertisement(16000); + // send out initial zero hop Advertisement to the mesh + the_mesh.sendSelfAdvertisement(16000, false); } void loop() { diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index bb62e61837..22a3d208b5 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -675,10 +675,14 @@ bool MyMesh::formatFileSystem() { #endif } -void MyMesh::sendSelfAdvertisement(int delay_millis) { +void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { - sendFlood(pkt, delay_millis); + if (flood) { + sendFlood(pkt, delay_millis); + } else { + sendZeroHop(pkt, delay_millis); + } } else { MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index f6adf01e09..4f3ed0e45c 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -177,7 +177,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; bool formatFileSystem() override; - void sendSelfAdvertisement(int delay_millis) override; + void sendSelfAdvertisement(int delay_millis, bool flood) override; void updateAdvertTimer() override; void updateFloodAdvertTimer() override; diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 1a3b4d6e0f..2c76ba0cc7 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -76,8 +76,8 @@ void setup() { ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif - // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvertisement(16000); + // send out initial zero hop Advertisement to the mesh + the_mesh.sendSelfAdvertisement(16000, false); } void loop() { diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 201532b959..8e27323edd 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -787,10 +787,14 @@ void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t revert_radio_at = futureMillis(2000 + timeout_mins*60*1000); // schedule when to revert radio params } -void SensorMesh::sendSelfAdvertisement(int delay_millis) { +void SensorMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet* pkt = createSelfAdvert(); if (pkt) { - sendFlood(pkt, delay_millis); + if (flood) { + sendFlood(pkt, delay_millis); + } else { + sendZeroHop(pkt, delay_millis); + } } else { MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 6046a1df7a..eb2d90c5a0 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -60,7 +60,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { NodePrefs* getNodePrefs() { return &_prefs; } void savePrefs() override { _cli.savePrefs(_fs); } bool formatFileSystem() override; - void sendSelfAdvertisement(int delay_millis) override; + void sendSelfAdvertisement(int delay_millis, bool flood) override; void updateAdvertTimer() override; void updateFloodAdvertTimer() override; void setLoggingOn(bool enable) override { } diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index a5fcc1484f..ab2842e0a5 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -110,8 +110,8 @@ void setup() { ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif - // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvertisement(16000); + // send out initial zero hop Advertisement to the mesh + the_mesh.sendSelfAdvertisement(16000, false); } void loop() { diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index a8dd9d0900..db83028568 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -197,7 +197,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return } else if (memcmp(command, "advert", 6) == 0) { - _callbacks->sendSelfAdvertisement(1500); // longer delay, give CLI response time to be sent first + // send flood advert + _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first strcpy(reply, "OK - Advert sent"); } else if (memcmp(command, "clock sync", 10) == 0) { uint32_t curr = getRTCClock()->getCurrentTime(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index b053010219..8661d1e6d8 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -61,7 +61,7 @@ class CommonCLICallbacks { virtual const char* getBuildDate() = 0; virtual const char* getRole() = 0; virtual bool formatFileSystem() = 0; - virtual void sendSelfAdvertisement(int delay_millis) = 0; + virtual void sendSelfAdvertisement(int delay_millis, bool flood) = 0; virtual void updateAdvertTimer() = 0; virtual void updateFloodAdvertTimer() = 0; virtual void setLoggingOn(bool enable) = 0; From d13bc446de689277ce59208c9bcac90ad8e58453 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Mon, 26 Jan 2026 22:39:39 +1300 Subject: [PATCH 283/409] added build flag to enable/disable boot advert --- examples/simple_repeater/main.cpp | 2 ++ examples/simple_room_server/main.cpp | 2 ++ examples/simple_secure_chat/main.cpp | 2 ++ examples/simple_sensor/main.cpp | 2 ++ platformio.ini | 1 + 5 files changed, 9 insertions(+) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d7e10fe28e..d55d611865 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -88,7 +88,9 @@ void setup() { #endif // send out initial zero hop Advertisement to the mesh +#if ENABLE_ADVERT_ON_BOOT == 1 the_mesh.sendSelfAdvertisement(16000, false); +#endif } void loop() { diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 2c76ba0cc7..825fb007d5 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -77,7 +77,9 @@ void setup() { #endif // send out initial zero hop Advertisement to the mesh +#if ENABLE_ADVERT_ON_BOOT == 1 the_mesh.sendSelfAdvertisement(16000, false); +#endif } void loop() { diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index da1bac5b32..018ec2a20f 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -582,7 +582,9 @@ void setup() { the_mesh.showWelcome(); // send out initial Advertisement to the mesh +#if ENABLE_ADVERT_ON_BOOT == 1 the_mesh.sendSelfAdvert(1200); // add slight delay +#endif } void loop() { diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index ab2842e0a5..330adcc2e4 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -111,7 +111,9 @@ void setup() { #endif // send out initial zero hop Advertisement to the mesh +#if ENABLE_ADVERT_ON_BOOT == 1 the_mesh.sendSelfAdvertisement(16000, false); +#endif } void loop() { diff --git a/platformio.ini b/platformio.ini index 75d37e869b..743e357afe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,6 +27,7 @@ build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 -D LORA_FREQ=869.525 -D LORA_BW=250 -D LORA_SF=11 + -D ENABLE_ADVERT_ON_BOOT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 ; NOTE: comment these out for more secure firmware -D ENABLE_PRIVATE_KEY_EXPORT=1 -D RADIOLIB_EXCLUDE_CC1101=1 From 7e24bd00b9f768f3c537a0514263b85257fdffb4 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Mon, 26 Jan 2026 23:05:10 +1300 Subject: [PATCH 284/409] increase maximum flood advert interval to 168 hours (7 days) --- src/helpers/CommonCLI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index db83028568..93baad5bee 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -422,8 +422,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "OK"); } else if (memcmp(config, "flood.advert.interval ", 22) == 0) { int hours = _atoi(&config[22]); - if ((hours > 0 && hours < 3) || (hours > 48)) { - strcpy(reply, "Error: interval range is 3-48 hours"); + if ((hours > 0 && hours < 3) || (hours > 168)) { + strcpy(reply, "Error: interval range is 3-168 hours"); } else { _prefs->flood_advert_interval = (uint8_t)(hours); _callbacks->updateFloodAdvertTimer(); From 0805a47f35972636cac41466586771ba2e7c51a6 Mon Sep 17 00:00:00 2001 From: Matthias Wientapper Date: Sat, 24 Jan 2026 21:56:33 +0100 Subject: [PATCH 285/409] Add output of region cmd via lora cli Add cli commands "region list {allowed|denied}" --- examples/simple_repeater/MyMesh.cpp | 23 +++++++++- src/helpers/RegionMap.cpp | 68 ++++++++++++++++++++++++++--- src/helpers/RegionMap.h | 6 ++- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b30072b85c..b063400829 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1068,8 +1068,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply const char* parts[4]; int n = mesh::Utils::parseTextParts(command, parts, 4, ' '); - if (n == 1 && sender_timestamp == 0) { - region_map.exportTo(Serial); + if (n == 1) { + region_map.exportTo(reply, 160); } else if (n >= 2 && strcmp(parts[1], "load") == 0) { temp_map.resetFrom(region_map); // rebuild regions in a temp instance memset(load_stack, 0, sizeof(load_stack)); @@ -1142,6 +1142,25 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - not found"); } + } else if (n >= 3 && strcmp(parts[1], "list") == 0) { + uint8_t mask = 0; + bool invert = false; + + if (strcmp(parts[2], "allowed") == 0) { + mask = REGION_DENY_FLOOD; + invert = false; // list regions that DON'T have DENY flag + } else if (strcmp(parts[2], "denied") == 0) { + mask = REGION_DENY_FLOOD; + invert = true; // list regions that DO have DENY flag + } else { + strcpy(reply, "Err - use 'allowed' or 'denied'"); + return; + } + + int len = region_map.exportNamesTo(reply, 160, mask, invert); + if (len == 0) { + strcpy(reply, "-none-"); + } } else { strcpy(reply, "Err - ??"); } diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 35692762ba..4ff8233efd 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -2,6 +2,45 @@ #include #include +// helper class for region map exporter, we emulate Stream with a safe buffer writer. + +class BufStream : public Stream { +public: + BufStream(char *buf, size_t max) + : _buf(buf), _max(max), _pos(0) { + if (_max > 0) _buf[0] = 0; + } + + size_t write(uint8_t c) override { + if (_pos + 1 >= _max) return 0; + _buf[_pos++] = c; + _buf[_pos] = 0; + return 1; + } + + size_t write(const uint8_t *buffer, size_t size) override { + size_t written = 0; + while (written < size) { + if (!write(buffer[written])) break; + written++; + } + return written; + } + + int available() override { return 0; } + int read() override { return -1; } + int peek() override { return -1; } + void flush() override {} + + size_t length() const { return _pos; } + +private: + char *_buf; + size_t _max; + size_t _pos; +}; + + RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { next_id = 1; num_regions = 0; home_id = 0; wildcard.id = wildcard.parent = 0; @@ -249,25 +288,40 @@ void RegionMap::exportTo(Stream& out) const { printChildRegions(0, &wildcard, out); // recursive } -int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) { +size_t RegionMap::exportTo(char *dest, size_t max_len) const { + if (!dest || max_len == 0) return 0; + + BufStream bs(dest, max_len); + exportTo(bs); // ← reuse existing logic + return bs.length(); +} + +int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert) { char *dp = dest; - if ((wildcard.flags & mask) == 0) { + + // Check wildcard region + bool wildcard_matches = invert ? (wildcard.flags & mask) : !(wildcard.flags & mask); + if (wildcard_matches) { *dp++ = '*'; *dp++ = ','; } - for (int i = 0; i < num_regions; i++) { + for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param) - const char* name = skip_hash(region->name); - int len = strlen(name); + + // Check if region matches the filter criteria + bool region_matches = invert ? (region->flags & mask) : !(region->flags & mask); + + if (region_matches) { + int len = strlen(skip_hash(region->name)); if ((dp - dest) + len + 2 < max_len) { // only append if name will fit - memcpy(dp, name, len); + memcpy(dp, skip_hash(region->name), len); dp += len; *dp++ = ','; } } } + if (dp > dest) { dp--; } // don't include trailing comma *dp = 0; // set null terminator diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 01174d0949..3ebff1ba5e 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -49,7 +49,9 @@ class RegionMap { int getCount() const { return num_regions; } const RegionEntry* getByIdx(int i) const { return ®ions[i]; } const RegionEntry* getRoot() const { return &wildcard; } - int exportNamesTo(char *dest, int max_len, uint8_t mask); + int exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert = false); - void exportTo(Stream& out) const; + void exportTo(Stream& out) const; + size_t exportTo(char *dest, size_t max_len) const; + }; From 5a20e8674f59697f46d5f8012eec8c6097b372c8 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 27 Jan 2026 14:04:12 +1100 Subject: [PATCH 286/409] support for meshtiny --- boards/meshtiny.json | 74 ++++++++++++++++++++++ variants/meshtiny/MeshtinyBoard.cpp | 44 +++++++++++++ variants/meshtiny/MeshtinyBoard.h | 66 +++++++++++++++++++ variants/meshtiny/platformio.ini | 68 ++++++++++++++++++++ variants/meshtiny/target.cpp | 47 ++++++++++++++ variants/meshtiny/target.h | 33 ++++++++++ variants/meshtiny/variant.cpp | 51 +++++++++++++++ variants/meshtiny/variant.h | 98 +++++++++++++++++++++++++++++ 8 files changed, 481 insertions(+) create mode 100644 boards/meshtiny.json create mode 100644 variants/meshtiny/MeshtinyBoard.cpp create mode 100644 variants/meshtiny/MeshtinyBoard.h create mode 100644 variants/meshtiny/platformio.ini create mode 100644 variants/meshtiny/target.cpp create mode 100644 variants/meshtiny/target.h create mode 100644 variants/meshtiny/variant.cpp create mode 100644 variants/meshtiny/variant.h diff --git a/boards/meshtiny.json b/boards/meshtiny.json new file mode 100644 index 0000000000..0418dc3bb8 --- /dev/null +++ b/boards/meshtiny.json @@ -0,0 +1,74 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "Meshtiny", + "mcu": "nrf52840", + "variant": "meshtiny", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": [ + "arduino", + "freertos" + ], + "name": "Meshtiny", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://shop.mtoolstec.com/product/meshtiny", + "vendor": "MTools Tec" +} diff --git a/variants/meshtiny/MeshtinyBoard.cpp b/variants/meshtiny/MeshtinyBoard.cpp new file mode 100644 index 0000000000..a2cdcb089d --- /dev/null +++ b/variants/meshtiny/MeshtinyBoard.cpp @@ -0,0 +1,44 @@ +#include "MeshtinyBoard.h" + +#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 MeshtinyBoard::begin() { + NRF52BoardDCDC::begin(); + btn_prev_state = HIGH; + + pinMode(PIN_VBAT_READ, INPUT); // VBAT ADC input + + // Set all button pins to INPUT_PULLUP + pinMode(PIN_BUTTON1, INPUT_PULLUP); + pinMode(PIN_BUTTON2, INPUT_PULLUP); + pinMode(PIN_BUTTON3, INPUT_PULLUP); + pinMode(PIN_BUTTON4, INPUT_PULLUP); + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + + diff --git a/variants/meshtiny/MeshtinyBoard.h b/variants/meshtiny/MeshtinyBoard.h new file mode 100644 index 0000000000..a73c9ea367 --- /dev/null +++ b/variants/meshtiny/MeshtinyBoard.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +class MeshtinyBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +protected: + uint8_t btn_prev_state; + +public: + MeshtinyBoard() : NRF52BoardOTA("Meshtiny OTA") {} + void begin(); + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } +#endif + + uint16_t getBattMilliVolts() override { + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT_READ); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char *getManufacturerName() const override { return "Meshtiny"; } + + void reboot() override { NVIC_SystemReset(); } + + void powerOff() override { + +#ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW) { + delay(10); + } +#endif + +#ifdef PIN_3V3_EN + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, LOW); +#endif + + +#ifdef PIN_LED1 + digitalWrite(PIN_LED1, LOW); +#endif + +#ifdef PIN_LED2 + digitalWrite(PIN_LED2, LOW); +#endif + +#ifdef PIN_USER_BTN + nrf_gpio_cfg_sense_input(g_ADigitalPinMap[PIN_USER_BTN], NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); +#endif + + sd_power_system_off(); + } + +}; diff --git a/variants/meshtiny/platformio.ini b/variants/meshtiny/platformio.ini new file mode 100644 index 0000000000..14e5c60d92 --- /dev/null +++ b/variants/meshtiny/platformio.ini @@ -0,0 +1,68 @@ +[Meshtiny] +extends = nrf52_base +board = meshtiny +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_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/meshtiny + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_3V3_EN=34 + -D MESHTINY + -D UI_HAS_JOYSTICK +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/meshtiny> + + + + + + +lib_deps = + ${nrf52_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:Meshtiny_companion_radio_usb] +extends = Meshtiny +build_flags = + ${Meshtiny.build_flags} + -I examples/companion_radio/ui-new + -D MESHTINY + -D PIN_BUZZER=30 + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Meshtiny.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Meshtiny.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Meshtiny_companion_radio_ble] +extends = Meshtiny +build_flags = + ${Meshtiny.build_flags} + -I examples/companion_radio/ui-new + -D MESHTINY + -D PIN_BUZZER=30 + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D BLE_DEBUG_LOGGING=1 +build_src_filter = ${Meshtiny.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Meshtiny.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/meshtiny/target.cpp b/variants/meshtiny/target.cpp new file mode 100644 index 0000000000..5fc60eae46 --- /dev/null +++ b/variants/meshtiny/target.cpp @@ -0,0 +1,47 @@ +#include "target.h" + +#include +#include + +MeshtinyBoard board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(); + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; + MomentaryButton user_btn(ENCODER_PRESS, 1000, true, true); + MomentaryButton joystick_left(ENCODER_LEFT, 1000, true, true); + MomentaryButton joystick_right(ENCODER_RIGHT, 1000, true, true); + MomentaryButton back_btn(PIN_SIDE_BUTTON, 1000, true, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/meshtiny/target.h b/variants/meshtiny/target.h new file mode 100644 index 0000000000..8ee3ee86eb --- /dev/null +++ b/variants/meshtiny/target.h @@ -0,0 +1,33 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#include +#include +#endif +#include + +extern MeshtinyBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; +extern MomentaryButton joystick_left; +extern MomentaryButton joystick_right; +extern MomentaryButton back_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/meshtiny/variant.cpp b/variants/meshtiny/variant.cpp new file mode 100644 index 0000000000..7cec7dec33 --- /dev/null +++ b/variants/meshtiny/variant.cpp @@ -0,0 +1,51 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" + +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() { + // LED1 & LED2 +#ifdef PIN_LED1 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); +#endif + +#ifdef PIN_LED2 + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); +#endif + + // 3V3 Power Rail - nothing connected on meshtiny + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, LOW); +} diff --git a/variants/meshtiny/variant.h b/variants/meshtiny/variant.h new file mode 100644 index 0000000000..daa8eff562 --- /dev/null +++ b/variants/meshtiny/variant.h @@ -0,0 +1,98 @@ +#ifndef _MESHTINY_H_ +#define _MESHTINY_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) // Green LED +#define PIN_LED2 (36) // Blue LED + +#define LED_RED (-1) +#define LED_GREEN PIN_LED1 +#define LED_BLUE (-1) // Disable annoying flashing caused by Bluefruit + +#define P_LORA_TX_LED PIN_LED2 // Blue LED +// #define PIN_STATUS_LED LED_GREEN // disable status led. +#define LED_BUILTIN LED_GREEN +#define PIN_LED LED_BUILTIN +#define LED_PIN LED_BUILTIN +#define LED_STATE_ON HIGH + +// Buttons +#define PIN_BUTTON1 (9) // side button +#define PIN_BUTTON2 (4) // encoder left +#define PIN_BUTTON3 (26) // encoder right +#define PIN_BUTTON4 (28) // encoder press +#define PIN_SIDE_BUTTON PIN_BUTTON1 +#define ENCODER_LEFT PIN_BUTTON2 +#define ENCODER_RIGHT PIN_BUTTON3 +#define ENCODER_PRESS PIN_BUTTON4 +#define PIN_USER_BTN PIN_SIDE_BUTTON + +// VBAT sensing +#define PIN_VBAT_READ (5) +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 +#define ADC_RESOLUTION 14 + +// Serial interfaces +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) +#define PIN_SERIAL2_RX (8) // Connected to Jlink CDC +#define PIN_SERIAL2_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (29) +#define PIN_SPI1_MOSI (30) +#define PIN_SPI1_SCK (3) + +// LoRa SX1262 module pins +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI +#define P_LORA_DIO_1 (47) +#define P_LORA_RESET (38) +#define P_LORA_BUSY (46) +#define P_LORA_NSS (42) +#define SX126X_POWER_EN (37) + +#define SX126X_RXEN RADIOLIB_NC +#define SX126X_TXEN RADIOLIB_NC + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT 1 +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) +#define PIN_BOARD_SDA (13) +#define PIN_BOARD_SCL (14) + +// Power control +#define PIN_3V3_EN (34) // nothing connected on meshtiny board + +#endif // _MESHTINY_H_ From 562750098832bf7fc62ec8e39e02e6e8704562ed Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 27 Jan 2026 15:22:18 +1100 Subject: [PATCH 287/409] * new "clkreboot" CLI command --- src/helpers/CommonCLI.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 93baad5bee..42198b4986 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -196,6 +196,10 @@ uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) { void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return + } else if (memcmp(command, "clkreboot", 9) == 0) { + // Reset clock + getRTCClock()->setCurrentTime(1715770351); // 15 May 2024, 8:50pm + _board->reboot(); // doesn't return } else if (memcmp(command, "advert", 6) == 0) { // send flood advert _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first From 5ff6e813bd62f8565eee36eabf37e3b25e12f48e Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 27 Jan 2026 18:16:21 +1100 Subject: [PATCH 288/409] * Fix: RegionMap build fail on _max --- src/helpers/RegionMap.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 4ff8233efd..2cc47e1d5a 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -6,13 +6,13 @@ class BufStream : public Stream { public: - BufStream(char *buf, size_t max) - : _buf(buf), _max(max), _pos(0) { - if (_max > 0) _buf[0] = 0; + BufStream(char *buf, size_t max_len) + : _buf(buf), _max_len(max_len), _pos(0) { + if (_max_len > 0) _buf[0] = 0; } size_t write(uint8_t c) override { - if (_pos + 1 >= _max) return 0; + if (_pos + 1 >= _max_len) return 0; _buf[_pos++] = c; _buf[_pos] = 0; return 1; @@ -36,7 +36,7 @@ class BufStream : public Stream { private: char *_buf; - size_t _max; + size_t _max_len; size_t _pos; }; From 4a83a6658a83baf96d39e7530b8e8158a577a382 Mon Sep 17 00:00:00 2001 From: taco Date: Wed, 28 Jan 2026 00:59:42 +1100 Subject: [PATCH 289/409] build fix for meshtiny (nrf52board ota refactor) --- variants/meshtiny/MeshtinyBoard.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/meshtiny/MeshtinyBoard.h b/variants/meshtiny/MeshtinyBoard.h index a73c9ea367..b69c0e419c 100644 --- a/variants/meshtiny/MeshtinyBoard.h +++ b/variants/meshtiny/MeshtinyBoard.h @@ -4,12 +4,12 @@ #include #include -class MeshtinyBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class MeshtinyBoard : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; public: - MeshtinyBoard() : NRF52BoardOTA("Meshtiny OTA") {} + MeshtinyBoard() : NRF52Board("Meshtiny OTA") {} void begin(); #if defined(P_LORA_TX_LED) From 3845a1c0219cc4011afa7671040c855eb7eeb44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=82ek?= Date: Tue, 27 Jan 2026 16:29:31 +0100 Subject: [PATCH 290/409] Fix incorrect INA260 address in debug message --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 8471d80db0..a75d378c86 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -284,7 +284,7 @@ bool EnvironmentSensorManager::begin() { INA260_initialized = true; } else { INA260_initialized = false; - MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA219_ADDRESS); + MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA260_ADDRESS); } #endif From 9665feeebfcf1e0b9236cc0d9c1b8ba0c39a5fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 27 Jan 2026 16:57:54 +0000 Subject: [PATCH 291/409] Update runArgs in devcontainer.json --- .devcontainer/devcontainer.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fcde504844..8440247e46 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,11 +10,12 @@ }, "runArgs": [ "--privileged", - // 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" + "--volume=/dev/bus/usb:/dev/bus/usb:ro", + // arch tty* is owned by uucp (986) + // debian tty* is owned by dialout (20) + "--group-add=20", + "--group-add=986" ], "postCreateCommand": { "platformio": "pipx install platformio" From edeafde51c5992ed67259901af013addd91a7f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 27 Jan 2026 19:36:12 +0000 Subject: [PATCH 292/409] Fix: Correct validation logic in isValidName function --- src/helpers/CommonCLI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 42198b4986..10ab866912 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -16,7 +16,7 @@ static uint32_t _atoi(const char* sp) { static bool isValidName(const char *n) { while (*n) { - if (*n == '[' || *n == ']' || *n == '/' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false; + if (*n == '[' || *n == ']' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false; n++; } return true; From 4e1e8bbffb49b5a90256aaa8fc731010ca28d5e7 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 26 Jan 2026 19:38:06 -0800 Subject: [PATCH 293/409] Add a cli command reference document --- docs/cli_commands.md | 881 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 881 insertions(+) create mode 100644 docs/cli_commands.md diff --git a/docs/cli_commands.md b/docs/cli_commands.md new file mode 100644 index 0000000000..6b4f61578b --- /dev/null +++ b/docs/cli_commands.md @@ -0,0 +1,881 @@ +# MeshCore Repeater & Room Server CLI Commands + +## Navigation + +- [Operational](#operational) +- [Neighbors](#neighbors-repeater-only) +- [Statistics](#statistics) +- [Logging](#logging) +- [Information](#info) +- [Configuration](#configuration) + - [Radio](#radio) + - [System](#system) + - [Routing](#routing) + - [ACL](#acl) + - [Region Management](#region-management-v110) + - [Region Examples](#region-examples) + - [GPS](#gps-when-gps-support-is-compiled-in) + - [Sensors](#sensors-when-sensor-support-is-compiled-in) + - [Bridge](#bridge-when-bridge-support-is-compiled-in) + +--- + +## Operational + +### Reboot the node +**Usage:** +- `reboot` + +--- + +### Reset the clock and reboot +**Usage:** +- `clkreboot` + +--- + +### Sync the clock with the remote device +**Usage:** +- `clock sync` + +--- + +### Display current time in UTC +**Usage:** +- `clock` + +--- + +### Set the time to a specific timestamp +**Usage:** +- `time ` + +**Parameters:** +- `epoc_seconds`: Unix epoc time + +--- + +### Send a flood advert +**Usage:** +- `advert` + +--- + +### Start an Over-The-Air (OTA) firmware update +**Usage:** +- `start ota` + +--- + +### Erase/Factory Reset +**Usage:** +- `erase` + +**Serial Only:** Yes + +**Warning:** _**This is destructive!**_ + +--- + +## Neighbors (Repeater Only) + +### List nearby neighbors +**Usage:** +- `neighbors` + +**Note:** The output of this command is limited to the 8 most recent adverts. + +**Note:** Each line is encoded as `{pubkey-prefix}:{timestamp}:{snr*4}` + +--- + +### Remove a neighbor +**Usage:** +- `neighbor.remove ` + +**Parameters:** +- `pubkey_prefix`: The public key of the node to remove from the neighbors list + +--- + +## Statistics + +### Clear Stats +**Usage:** `clear stats` + +--- + +### System Stats - Battery, Uptime, Queue Length and Debug Flags +**Usage:** +- `stats-core` + +**Serial Only:** Yes + +--- + +### Radio Stats - Noise floor, Last RSSI/SNR, Airtime, Receive errors +**Usage:** `stats-radio` + +**Serial Only:** Yes + +--- + +### Packet stats - Packet counters: Received, Sent +**Usage:** `stats-packets` + +**Serial Only:** Yes + +--- + +## Logging + +### Begin capture of rx log to node storage +**Usage:** `log start` + +--- + +### End capture of rx log to node sotrage +**Usage:** `log stop` + +--- + +### Erase captured log +**Usage:** `log erase` + +--- + +### Print the captured log to the serial terminal +**Usage:** `log` + +**Serial Only:** Yes + +--- + +## Info + +### Get the Version +**Usage:** `ver` + +--- + +### Show the hardware name +**Usage:** `board` + +--- + +## Configuration + +### Radio + +#### View or change this node's radio parameters +**Usage:** +- `get radio` +- `set radio ,,,` + +**Parameters:** +- `freq`: Frequency in MHz +- `bw`: Bandwidth in kHz +- `sf`: Spreading factor (5-12) +- `cr`: Coding rate (5-8) + +**Set by build flag:** `LORA_FREQ`, `LORA_BW`, `LORA_SF`, `LORA_CR` + +**Default:** `869.525,250,11,5` + +**Note:** Requires reboot to apply + +--- + +#### View or change this node's transmit power +**Usage:** +- `get tx` +- `set tx ` + +**Parameters:** +- `dbm`: Power level in dBm (1-22) + +**Set by build flag:** `LORA_TX_POWER` + +**Default:** Varies by board + +**Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Referr to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** + +--- + +#### Change the radio parameters for a set duration +**Usage:** +- `tempradio ,,,,` + +**Parameters:** +- `freq`: Frequency in MHz (300-2500) +- `bw`: Bandwidth in kHz (7.8-500) +- `sf`: Spreading factor (5-12) +- `cr`: Coding rate (5-8) +- `timeout_mins`: Duration in minutes (must be > 0) + +**Note:** This is not saved to preferences and will clear on reboot + +--- + +#### View or change this node's frequency +**Usage:** +- `get freq` +- `set freq ` + +**Parameters:** +- `frequency`: Frequency in MHz + +**Default:** `869.525` + +**Note:** Requires reboot to apply + +### System + +#### View or change this node's name +**Usage:** +- `get name` +- `set name ` + +**Parameters:** +- `name`: Node name + +**Set by build flag:** `ADVERT_NAME` + +**Default:** Varies by board + +**Note:** Max length varies. If a location is set, the max length is 24 bytes; 32 otherwise. Emoji and unicode characters may take more than one byte. + +--- + +#### View or change this node's latitude +**Usage:** +- `get lat` +- `set lat ` + +**Set by build flag:** `ADVERT_LAT` + +**Default:** `0` + +**Parameters:** +- `degrees`: Latitude in degrees + +--- + +#### View or change this node's longitude +**Usage:** +- `get lon` +- `set lon ` + +**Set by build flag:** `ADVERT_LON` + +**Default:** `0` + +**Parameters:** +- `degrees`: Longitude in degrees + +--- + +#### View or change this node's identity (Private Key) +**Usage:** +- `get prv.key` +- `set prv.key ` + +**Parameters:** +- `private_key`: Private key in hex format (64 hex characters) + +**Serial Only:** +- `get prv.key`: Yes +- `set prv.key`: No + +**Note:** Requires reboot to take effect after setting + +--- + +#### View or change this node's admin password +**Usage:** +- `get password` +- `set password ` + +**Parameters:** +- `password`: Admin password + +**Set by build flag:** `ADMIN_PASSWORD` + +**Default:** `password` + +**Note:** Echoed back for confirmation + +**Note:** Any node using this password will be added to the admin ACL list. + +--- + +#### View or change this node's guest password +**Usage:** +- `get guest.password` +- `set guest.password ` + +**Parameters:** +- `password`: Guest password + +**Set by build flag:** `ROOM_PASSWORD` (Room Server only) + +**Default:** `` + +--- + +#### View or change this node's owner info +**Usage:** +- `get owner.info` +- `set owner.info ` + +**Parameters:** +- `text`: Owner information text + +**Default:** `` + +**Note:** `|` characters are translated to newlines + +**Note:** Requires firmware 1.12.+ + +--- + +#### Fine-tune the battery reading +**Usage:** +- `get adc.multiplier` +- `set adc.multiplier ` + +**Parameters:** +- `value`: ADC multiplier (0.0-10.0) + +**Default:** `0.0` (value defined by board) + +**Note:** Returns "Error: unsupported by this board" if hardware doesn't support it + +--- + +#### View or change this node's power saving flag (Repeater Only) +**Usage:** +- `powersaving ` +- `powersaving` + +**Parameters:** +- `state`: `on`|`off` + +**Default:** `on` + +**Note:** When enabled, device enters sleep mode between radio transmissions + +--- + +### Routing + +#### View or change this node's repeat flag +**Usage:** +- `get repeat` +- `set repeat ` + +**Parameters:** + - `state`: `on`|`off` + +**Default:** `on` + +--- + +#### View or change the retransmit delay factor for flood traffic +**Usage:** +- `get txdelay` +- `set txdelay ` + +**Parameters:** +- `value`: Transmit delay factor (0-2) + +**Default:** `0.5` + +--- + +#### View or change the retransmit delay factor for direct traffic +**Usage:** +- `get direct.txdelay` +- `set direct.txdelay ` + +**Parameters:** +- `value`: Direct transmit delay factor (0-2) + +**Default:** `0.2` + +--- + +#### [Experimental] View or change the processing delay for received traffic +**Usage:** +- `get rxdelay` +- `set rxdelay ` + +**Parameters:** +- `value`: Receive delay base (0-20) + +**Default:** `0.0` + +--- + +#### View or change the airtime factor (duty cycle limit) +**Usage:** +- `get af` +- `set af ` + +**Parameters:** +- `value`: Airtime factor (0-9) + +**Default:** `1.0` + +--- + +#### View or change the local interference threshold +**Usage:** +- `get int.thresh` +- `set int.thresh ` + +**Parameters:** +- `value`: Interference threshold value + +**Default:** `0.0` + +--- + +#### View or change the AGC Reset Interval +**Usage:** +- `get agc.reset.interval` +- `set agc.reset.interval ` + +**Parameters:** +- `value`: Interval in seconds rounded down to a multiple of 4 (17 becomes 16) + +**Default:** `0.0` + +--- + +#### Enable or disable Multi-Acks support +**Usage:** +- `get multi.acks` +- `set multi.acks ` + +**Parameters:** +- `state`: `0` (disable) or `1` (enable) + +**Default:** `0` + +--- + +#### View or change the flood advert interval +**Usage:** +- `get flood.advert.interval` +- `set flood.advert.interval ` + +**Parameters:** +- `hours`: Interval in hours (3-168) + +**Default:** `12` (Repeater) - `0` (Sensor) + +--- + +#### View or change the zero-hop advert interval +**Usage:** +- `get advert.interval` +- `set advert.interval ` + +**Parameters:** +- `minutes`: Interval in minutes rounded down to the nearest multiple of 2 (61 becomes 60) (60-240) + +**Default:** `0` + +--- + +#### Limit the number of hops for a flood message +**Usage:** +- `get flood.max` +- `set flood.max ` + +**Parameters:** +- `value`: Maximum flood hop count (0-64) + +**Default:** `64` + +--- + +### ACL + +#### Add, update or remove permissions for a companion +**Usage:** +- `setperm ` + +**Parameters:** +- `pubkey`: Companion public key +- `permissions`: + - `0`: Guest + - `1`: Read-only + - `2`: Read-write + - `3`: Admin + +**Note:** Removes the entry when `permissions` is omitted + +--- + +#### View the current ACL +**Usage:** +- `get acl` + +**Serial Only:** Yes + +--- + +#### View or change this room server's 'read-only' flag +**Usage:** +- `get allow.read.only` +- `set allow.read.only ` + +**Parameters:** +- `state`: `on` (enable) or `off` (disable) + +**Default:** `off` + +--- + +### Region Management (v1.10.+) + +#### Bulk-load region lists +**Usage:** +- `region load` +- `region load [flood_flag]` + +**Parameters:** +- `name`: A name of a region. `*` represents the wildcard region + +**Note:** `flood_flag`: Optional `F` to allow flooding + +**Note:** Indentation creates parent-child relationships (max 8 levels) + +**Note:** `region load` with an empty name will not work remotely (it's interactive) + +--- + +#### Save any changes to regions made since reboot +**Usage:** +- `region save` + +--- + +#### Allow a region +**Usage:** +- `region allowf ` + +**Parameters:** +- `name`: Region name (or `*` for wildcard) + +**Note:** Setting on wildcard `*` allows packets without region transport codes + +--- + +#### Block a region +**Usage:** +- `region denyf ` + +**Parameters:** +- `name`: Region name (or `*` for wildcard) + +**Note:** Setting on wildcard `*` drops packets without region transport codes + +--- + +#### Show information for a region +**Usage:** +- `region get ` + +**Parameters:** +- `name`: Region name (or `*` for wildcard) + +--- + +#### View or change the home region for this node +**Usage:** +- `region home` +- `region home ` + +**Parameters:** +- `name`: Region name + +--- + +#### Create a new region +**Usage:** +- `region put [parent_name]` + +**Parameters:** +- `name`: Region name +- `parent_name`: Parent region name (optional, defaults to wildcard) + +--- + +#### Remove a region +**Usage:** +- `region remove ` + +**Parameters:** +- `name`: Region name + +**Note:** Must remove all child regions before the region can be removed + +--- + +#### View all regions +**Usage:** +- `region list ` + +**Serial Only:** Yes + +**Parameters:** +- `filter`: `allowed`|`denied` + +**Note:** Requires firmware 1.12.+ + +--- + +#### Dump all defined regions and flood permissions +**Usage:** +- `region` + +**Serial Only:** Yes + +--- + +### Region Examples + +**Example 1: Using F Flag with Named Public Region** +``` +region load +#Europe F + +region save +``` + +**Explanation:** +- Creates a region named `#Europe` with flooding enabled +- Packets from this region will be flooded to other nodes + +--- + +**Example 2: Using Wildcard with F Flag** +``` +region load +* F + +region save +``` + +**Explanation:** +- Creates a wildcard region `*` with flooding enabled +- Enables flooding for all regions automatically +- Applies only to packets without transport codes + +--- + +**Example 3: Using Wildcard Without F Flag** +``` +region load +* + +region save +``` +**Explanation:** +- Creates a wildcard region `*` without flooding +- This region exists but doesn't affect packet distribution +- Used as a default/empty region + +--- + +**Example 4: Nested Public Region with F Flag** +``` +region load +#Europe F + #UK + #London + #Manchester + #France + #Paris + #Lyon + +region save +``` + +**Explanation:** +- Creates `#Europe` region with flooding enabled +- Adds nested child regions (`#UK`, `#France`) +- All nested regions inherit the flooding flag from parent + +--- + +**Example 5: Wildcard with Nested Public Regions** +``` +region load +* F + #NorthAmerica + #USA + #NewYork + #California + #Canada + #Ontario + #Quebec + +region save +``` + +**Explanation:** +- Creates wildcard region `*` with flooding enabled +- Adds nested `#NorthAmerica` hierarchy +- Enables flooding for all child regions automatically +- Useful for global networks with specific regional rules + +--- +### GPS (When GPS support is compiled in) + +#### View or change GPS state +**Usage:** +- `gps` +- `gps ` + +**Parameters:** +- `state`: `on`|`off` + +**Default:** `off` + +**Note:** Output format: `{status}, {fix}, {sat count}` (when enabled) + +--- + +#### Sync this node's clock with GPS time +**Usage:** +- `gps sync` + +--- + +#### Set this node's location based on the GPS coordinates +**Usage:** +- `gps setloc` + +--- + +#### View or change the GPS advert policy +**Usage:** +- `gps advert` +- `gps advert ` + +**Parameters:** +- `policy`: `none`|`shared`|`prefs` + - `none`: don't include location in adverts + - `share`: share gps location (from SensorManager) + - `prefs`: location stored in node's lat and lon settings + +**Default:** `prefs` + +--- + +### Sensors (When sensor support is compiled in) + +#### View the list of sensors on this node +**Usage:** `sensor list [start]` + +**Parameters:** +- `start`: Optional starting index (defaults to 0) + +**Note:** Output format: `=\n` + +--- + +#### View or change thevalue of a sensor +**Usage:** +- `sensor get ` +- `sensor set ` + +**Parameters:** +- `key`: Sensor setting name +- `value`: The value to set the sensor to + +--- + +### Bridge (When bridge support is compiled in) + +#### View or change the bridge enabled flag +**Usage:** +- `get bridge.enabled` +- `set bridge.enabled ` + +**Parameters:** +- `state`: `on`|`off` + +**Default:** `off` + +--- + +#### View the bridge source +**Usage:** +- `get bridge.source` + +--- + +#### Add a delay to packets routed through this bridge +**Usage:** +- `get bridge.delay` +- `set bridge.delay ` + +**Parameters:** +- `ms`: Delay in milliseconds (0-10000) + +**Default:** `500` + +--- + +#### View or change the source of packets bridged to the external interface +**Usage:** +- `get bridge.source` +- `set bridge.source ` + +**Parameters:** +- `source`: + - `rx`: bridges received packets + - `tx`: bridges transmitted packets + +**Default:** `tx` + +--- + +#### View or change the speed of the bridge (RS-232 only) +**Usage:** +- `get bridge.baud` +- `set bridge.baud ` + +**Parameters:** +- `rate`: Baud rate (`9600`, `19200`, `38400`, `57600`, or `115200`) + +**Default:** `115200` + +--- + +#### View or change the channel used for bridging (ESPNow only) +**Usage:** +- `get bridge.channel` +- `set bridge.channel ` + +**Parameters:** +- `channel`: Channel number (1-14) + +--- + +#### Set the ESP-Now secret +**Usage:** +- `get bridge.secret` +- `set bridge.secret ` + +**Parameters:** +- `secret`: 16-character encryption secret + +**Default:** Varies by board + +--- From d5a73b239437ed6a1c9f9512246169e8238b25c8 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Wed, 28 Jan 2026 17:18:39 +0100 Subject: [PATCH 294/409] fix: build errors because of changes in NRF52 base class --- variants/rak3401/RAK3401Board.h | 32 ++++++-------------------------- variants/rak3401/variant.h | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 609393c338..20edf9069e 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -4,30 +4,6 @@ #include #include -// LoRa radio module pins for RAK13302 -#define P_LORA_SCLK 3 -#define P_LORA_MISO 29 -#define P_LORA_MOSI 30 -#define P_LORA_NSS 26 -#define P_LORA_DIO_1 10 -#define P_LORA_BUSY 9 -#define P_LORA_RESET 4 -#ifndef P_LORA_PA_EN - #define P_LORA_PA_EN 31 -#endif - -//#define PIN_GPS_SDA 13 //GPS SDA pin (output option) -//#define PIN_GPS_SCL 14 //GPS SCL pin (output option) -// #define PIN_GPS_TX 16 //GPS TX pin -// #define PIN_GPS_RX 15 //GPS RX pin -#define PIN_GPS_1PPS 17 //GPS PPS pin -#define GPS_BAUD_RATE 9600 -#define GPS_ADDRESS 0x42 //i2c address for GPS - -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - - // built-ins #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) @@ -35,9 +11,13 @@ #define PIN_3V3_EN (34) #define WB_IO2 PIN_3V3_EN -class RAK3401Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class RAK3401Board : public NRF52BoardDCDC { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif public: - RAK3401Board() : NRF52BoardOTA("RAK3401_OTA") {} + RAK3401Board() : NRF52Board("RAK3401_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index 9c18224798..56fe081694 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -141,11 +141,6 @@ static const uint8_t AREF = PIN_AREF; #define EXTERNAL_FLASH_DEVICES IS25LP080D #define EXTERNAL_FLASH_USE_QSPI -#define P_LORA_SCK PIN_SPI1_SCK -#define P_LORA_MISO PIN_SPI1_MISO -#define P_LORA_MOSI PIN_SPI1_MOSI -#define P_LORA_CS 26 - #define USE_SX1262 #define SX126X_CS (26) #define SX126X_DIO1 (10) @@ -157,6 +152,15 @@ static const uint8_t AREF = PIN_AREF; #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define P_LORA_SCLK PIN_SPI1_SCK +#define P_LORA_MISO PIN_SPI1_MISO +#define P_LORA_MOSI PIN_SPI1_MOSI +#define P_LORA_NSS SX126X_CS +#define P_LORA_DIO_1 SX126X_DIO1 +#define P_LORA_BUSY SX126X_BUSY +#define P_LORA_RESET SX126X_RESET +#define P_LORA_PA_EN 31 + // enables 3.3V periphery like GPS or IO Module // Do not toggle this for GPS power savings #define PIN_3V3_EN (34) @@ -173,6 +177,10 @@ static const uint8_t AREF = PIN_AREF; #define PIN_GPS_RX PIN_SERIAL1_RX #define PIN_GPS_TX PIN_SERIAL1_TX +#define PIN_GPS_1PPS PIN_GPS_PPS +#define GPS_BAUD_RATE 9600 +#define GPS_ADDRESS 0x42 //i2c address for GPS + // Battery // The battery sense is hooked to pin A0 (5) #define BATTERY_PIN PIN_A0 From f41872420e3166ffb8b5676d489934937717a4c4 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Wed, 28 Jan 2026 17:28:48 +0100 Subject: [PATCH 295/409] moved pindefs from board file to variant.h --- variants/rak4631/RAK4631Board.h | 21 ---------------- variants/rak4631/variant.h | 43 +++++++++++++++++++++++---------- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index ff4a5b7d42..7e67165b19 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -4,27 +4,6 @@ #include #include -// LoRa radio module pins for RAK4631 -#define P_LORA_DIO_1 47 -#define P_LORA_NSS 42 -#define P_LORA_RESET RADIOLIB_NC // 38 -#define P_LORA_BUSY 46 -#define P_LORA_SCLK 43 -#define P_LORA_MISO 45 -#define P_LORA_MOSI 44 -#define SX126X_POWER_EN 37 - -//#define PIN_GPS_SDA 13 //GPS SDA pin (output option) -//#define PIN_GPS_SCL 14 //GPS SCL pin (output option) -//#define PIN_GPS_TX 16 //GPS TX pin -//#define PIN_GPS_RX 15 //GPS RX pin -#define PIN_GPS_1PPS 17 //GPS PPS pin -#define GPS_BAUD_RATE 9600 -#define GPS_ADDRESS 0x42 //i2c address for GPS - -#define SX126X_DIO2_AS_RF_SWITCH true -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - // built-ins #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index b18335f8da..142d93e91d 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -144,6 +144,19 @@ extern "C" static const uint8_t MISO = PIN_SPI_MISO; static const uint8_t SCK = PIN_SPI_SCK; +// LoRa radio module pins for RAK4631 +#define P_LORA_DIO_1 (47) +#define P_LORA_NSS (42) +#define P_LORA_RESET (-1) +#define P_LORA_BUSY (46) +#define P_LORA_SCLK (43) +#define P_LORA_MISO (45) +#define P_LORA_MOSI (44) +#define SX126X_POWER_EN (37) + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + /* * Wire Interfaces */ @@ -155,19 +168,23 @@ extern "C" #define PIN_WIRE1_SDA (24) #define PIN_WIRE1_SCL (25) - // QSPI Pins - // QSPI occupied by GPIO's - #define PIN_QSPI_SCK 3 // 19 - #define PIN_QSPI_CS 26 // 17 - #define PIN_QSPI_IO0 30 // 20 - #define PIN_QSPI_IO1 29 // 21 - #define PIN_QSPI_IO2 28 // 22 - #define PIN_QSPI_IO3 2 // 23 - - // On-board QSPI Flash - // No onboard flash - #define EXTERNAL_FLASH_DEVICES IS25LP080D - #define EXTERNAL_FLASH_USE_QSPI +// QSPI Pins +// QSPI occupied by GPIO's +#define PIN_QSPI_SCK 3 // 19 +#define PIN_QSPI_CS 26 // 17 +#define PIN_QSPI_IO0 30 // 20 +#define PIN_QSPI_IO1 29 // 21 +#define PIN_QSPI_IO2 28 // 22 +#define PIN_QSPI_IO3 2 // 23 + +// On-board QSPI Flash +// No onboard flash +#define EXTERNAL_FLASH_DEVICES IS25LP080D +#define EXTERNAL_FLASH_USE_QSPI + +#define PIN_GPS_1PPS 17 //GPS PPS pin +#define GPS_BAUD_RATE 9600 +#define GPS_ADDRESS 0x42 //i2c address for GPS #ifdef __cplusplus } From dd2a9044f3c0a3e608eefc8325a2ea511973ba55 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Thu, 29 Jan 2026 08:02:26 +1100 Subject: [PATCH 296/409] Refactor display scaling definitions for HELTEC_VISION_MASTER_T190 --- src/helpers/ui/ST7789Display.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index 7ea351879d..f7d20b8ab7 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -10,8 +10,13 @@ #define Y_OFFSET 1 // Vertical offset to prevent top row cutoff #endif -#define SCALE_X 1.875f // 240 / 128 -#define SCALE_Y 2.109375f // 135 / 64 +#ifdef HELTEC_VISION_MASTER_T190 + #define SCALE_X 2.5f // 320 / 128 + #define SCALE_Y 2.65625f // 170 / 64 +#else + #define SCALE_X 1.875f // 240 / 128 + #define SCALE_Y 2.109375f // 135 / 64 +#endif bool ST7789Display::begin() { if(!_isOn) { From f7e54ea7971121ab86501962f3e283889fafcf52 Mon Sep 17 00:00:00 2001 From: Steven Linn Date: Wed, 28 Jan 2026 13:24:22 -0700 Subject: [PATCH 297/409] Add LilyGO T-Beam 1W Support --- boards/t_beam_1w.json | 50 ++++++ variants/lilygo_tbeam_1w/TBeam1WBoard.cpp | 71 ++++++++ variants/lilygo_tbeam_1w/TBeam1WBoard.h | 45 ++++++ variants/lilygo_tbeam_1w/pins_arduino.h | 26 +++ variants/lilygo_tbeam_1w/platformio.ini | 189 ++++++++++++++++++++++ variants/lilygo_tbeam_1w/target.cpp | 64 ++++++++ variants/lilygo_tbeam_1w/target.h | 27 ++++ variants/lilygo_tbeam_1w/variant.h | 96 +++++++++++ 8 files changed, 568 insertions(+) create mode 100644 boards/t_beam_1w.json create mode 100644 variants/lilygo_tbeam_1w/TBeam1WBoard.cpp create mode 100644 variants/lilygo_tbeam_1w/TBeam1WBoard.h create mode 100644 variants/lilygo_tbeam_1w/pins_arduino.h create mode 100644 variants/lilygo_tbeam_1w/platformio.ini create mode 100644 variants/lilygo_tbeam_1w/target.cpp create mode 100644 variants/lilygo_tbeam_1w/target.h create mode 100644 variants/lilygo_tbeam_1w/variant.h diff --git a/boards/t_beam_1w.json b/boards/t_beam_1w.json new file mode 100644 index 0000000000..2f1159aa1b --- /dev/null +++ b/boards/t_beam_1w.json @@ -0,0 +1,50 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DLILYGO_TBEAM_1W", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + [ + "0x303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "lilygo_tbeam_1w" + }, + "connectivity": [ + "wifi", + "bluetooth", + "lora" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "LilyGo TBeam-1W", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "http://www.lilygo.cn/", + "vendor": "LilyGo" +} diff --git a/variants/lilygo_tbeam_1w/TBeam1WBoard.cpp b/variants/lilygo_tbeam_1w/TBeam1WBoard.cpp new file mode 100644 index 0000000000..1719d73334 --- /dev/null +++ b/variants/lilygo_tbeam_1w/TBeam1WBoard.cpp @@ -0,0 +1,71 @@ +#include "TBeam1WBoard.h" + +void TBeam1WBoard::begin() { + ESP32Board::begin(); + + // Power on radio module (must be done before radio init) + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + radio_powered = true; + delay(10); // Allow radio to power up + + // RF switch RXEN pin handled by RadioLib via setRfSwitchPins() + + // Initialize LED + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // Initialize fan control (on by default - 1W PA can overheat) + pinMode(FAN_CTRL_PIN, OUTPUT); + digitalWrite(FAN_CTRL_PIN, HIGH); +} + +void TBeam1WBoard::onBeforeTransmit() { + // RF switching handled by RadioLib via SX126X_DIO2_AS_RF_SWITCH and setRfSwitchPins() + digitalWrite(LED_PIN, HIGH); // TX LED on +} + +void TBeam1WBoard::onAfterTransmit() { + digitalWrite(LED_PIN, LOW); // TX LED off +} + +uint16_t TBeam1WBoard::getBattMilliVolts() { + // T-Beam 1W uses 7.4V battery with voltage divider + // ADC reads through divider - adjust multiplier based on actual divider ratio + analogReadResolution(12); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(BATTERY_PIN); + } + raw = raw / 8; + // Assuming voltage divider ratio from ADC_MULTIPLIER + // 3.3V reference, 12-bit ADC (4095 max) + return static_cast((raw * 3300 * ADC_MULTIPLIER) / 4095); +} + +const char* TBeam1WBoard::getManufacturerName() const { + return "LilyGo T-Beam 1W"; +} + +void TBeam1WBoard::powerOff() { + // Turn off radio LNA (CTRL pin must be LOW when not receiving) + digitalWrite(SX126X_RXEN, LOW); + + // Turn off radio power + digitalWrite(SX126X_POWER_EN, LOW); + radio_powered = false; + + // Turn off LED and fan + digitalWrite(LED_PIN, LOW); + digitalWrite(FAN_CTRL_PIN, LOW); + + ESP32Board::powerOff(); +} + +void TBeam1WBoard::setFanEnabled(bool enabled) { + digitalWrite(FAN_CTRL_PIN, enabled ? HIGH : LOW); +} + +bool TBeam1WBoard::isFanEnabled() const { + return digitalRead(FAN_CTRL_PIN) == HIGH; +} diff --git a/variants/lilygo_tbeam_1w/TBeam1WBoard.h b/variants/lilygo_tbeam_1w/TBeam1WBoard.h new file mode 100644 index 0000000000..d999dfd4c1 --- /dev/null +++ b/variants/lilygo_tbeam_1w/TBeam1WBoard.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include "variant.h" + +// LilyGo T-Beam 1W with SX1262 + external PA (XY16P35 module) +// +// Power architecture (LDO is separate chip on T-Beam board, not inside XY16P35): +// +// VCC (+4.0~+8.0V) ──┬──────────────────► XY16P35 VCC pin 5 (PA direct) +// (USB or Battery) │ +// │ ┌───────────┐ +// └──►│ LDO Chip │──► +3.3V ──► XY16P35 (SX1262 + LNA) +// │ EN=GPIO40 │ +// └───────────┘ +// LDO_EN (GPIO 40): H @ +1.2V~VIN, active high, not floating +// +// Control signals: +// - LDO_EN (GPIO 40): HIGH enables LDO → powers SX1262 + LNA +// - TCXO_EN (DIO3): HIGH enables TCXO (set to 1.8V per Meshtastic) +// - CTL (GPIO 21): HIGH=RX (LNA on), LOW=TX (LNA off) +// - DIO2: AUTO via SX126X_DIO2_AS_RF_SWITCH (TX path) +// +// Power notes: +// - PA needs VCC 4.0-8.0V for full 32dBm output +// - USB-C (3.9-6V) marginal; 7.4V battery recommended +// - Battery must support 2A+ discharge for high-power TX + +class TBeam1WBoard : public ESP32Board { +private: + bool radio_powered = false; + +public: + void begin(); + void onBeforeTransmit() override; + void onAfterTransmit() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override; + void powerOff() override; + + // Fan control methods + void setFanEnabled(bool enabled); + bool isFanEnabled() const; +}; diff --git a/variants/lilygo_tbeam_1w/pins_arduino.h b/variants/lilygo_tbeam_1w/pins_arduino.h new file mode 100644 index 0000000000..c6f596f432 --- /dev/null +++ b/variants/lilygo_tbeam_1w/pins_arduino.h @@ -0,0 +1,26 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial (USB CDC) +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +// I2C for OLED and sensors +static const uint8_t SDA = 8; +static const uint8_t SCL = 9; + +// Default SPI mapped to Radio/SD +static const uint8_t SS = 15; // LoRa CS +static const uint8_t MOSI = 11; +static const uint8_t MISO = 12; +static const uint8_t SCK = 13; + +// SD Card CS +#define SDCARD_CS 10 + +#endif /* Pins_Arduino_h */ diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini new file mode 100644 index 0000000000..4b72b5e730 --- /dev/null +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -0,0 +1,189 @@ +[LilyGo_TBeam_1W] +extends = esp32_base +board = t_beam_1w +build_flags = + ${esp32_base.build_flags} + -I variants/lilygo_tbeam_1w + -D TBEAM_1W + + ; Radio - SX1262 with high-power PA (32dBm max output) + ; Note: Set SX1262 output to 22dBm max, external PA provides additional gain + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=1 + -D P_LORA_NSS=15 + -D P_LORA_RESET=3 + -D P_LORA_BUSY=38 + -D P_LORA_SCLK=13 + -D P_LORA_MISO=12 + -D P_LORA_MOSI=11 + + ; RF switch configuration: + ; DIO2 controls TX path (PA enable) via SX126X_DIO2_AS_RF_SWITCH + ; GPIO21 controls RX path (LNA enable) via SX126X_RXEN + ; Truth table: DIO2=1,RXEN=0 → TX | DIO2=0,RXEN=1 → RX + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_RXEN=21 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + + ; TX power: 22dBm to SX1262, PA module adds ~10dB for 32dBm total + -D LORA_TX_POWER=22 + + ; Display - SH1106 OLED at 0x3C + -D DISPLAY_CLASS=SH1106Display + + ; I2C pins + -D PIN_BOARD_SDA=8 + -D PIN_BOARD_SCL=9 + + ; GPS - L76K module + ; GNSS_TXD (IO5) = GPS transmits → MCU RX + ; GNSS_RXD (IO6) = GPS receives → MCU TX + -D PIN_GPS_TX=5 + -D PIN_GPS_RX=6 + -D PIN_GPS_EN=16 + -D ENV_INCLUDE_GPS=1 + + ; User interface + -D PIN_USER_BTN=17 + +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/lilygo_tbeam_1w> + + + + + + + +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SH110X @ ~2.1.13 + stevemarple/MicroNMEA @ ~2.0.6 + +; === LILYGO T-Beam 1W Repeater === +[env:LilyGo_TBeam_1W_repeater] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -D ADVERT_NAME='"T-Beam 1W Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + ${esp32_ota.lib_deps} + +; === LILYGO T-Beam 1W Room Server === +[env:LilyGo_TBeam_1W_room_server] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -D ADVERT_NAME='"T-Beam 1W Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + ${esp32_ota.lib_deps} + +; === LILYGO T-Beam 1W Companion Radio (USB) === +[env:LilyGo_TBeam_1W_companion_radio_usb] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; === LILYGO T-Beam 1W Companion Radio (BLE) === +[env:LilyGo_TBeam_1W_companion_radio_ble] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; === LILYGO T-Beam 1W Companion Radio (WiFi) === +[env:LilyGo_TBeam_1W_companion_radio_wifi] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; === LILYGO T-Beam 1W Repeater with ESPNow Bridge === +[env:LilyGo_TBeam_1W_repeater_bridge_espnow] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -D ADVERT_NAME='"T-Beam 1W ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/lilygo_tbeam_1w/target.cpp b/variants/lilygo_tbeam_1w/target.cpp new file mode 100644 index 0000000000..fcdb42ed80 --- /dev/null +++ b/variants/lilygo_tbeam_1w/target.cpp @@ -0,0 +1,64 @@ +#include +#include "target.h" + +TBeam1WBoard board; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +static SPIClass spi; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + // Initialize SPI for radio + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + + // GPS serial initialized by EnvironmentSensorManager::begin() + + bool success = radio.std_init(&spi); + if (success) { + // T-Beam 1W has external PA requiring longer ramp time (>800us recommended) + // RADIOLIB_SX126X_PA_RAMP_800U = 0x05 + radio.setTxParams(LORA_TX_POWER, RADIOLIB_SX126X_PA_RAMP_800U); + } + return 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); +} diff --git a/variants/lilygo_tbeam_1w/target.h b/variants/lilygo_tbeam_1w/target.h new file mode 100644 index 0000000000..2c3e8970ad --- /dev/null +++ b/variants/lilygo_tbeam_1w/target.h @@ -0,0 +1,27 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include "TBeam1WBoard.h" + +#ifdef DISPLAY_CLASS + #include + #include + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +extern TBeam1WBoard 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/lilygo_tbeam_1w/variant.h b/variants/lilygo_tbeam_1w/variant.h new file mode 100644 index 0000000000..c05b16966e --- /dev/null +++ b/variants/lilygo_tbeam_1w/variant.h @@ -0,0 +1,96 @@ +// LilyGo T-Beam-1W variant.h +// Configuration based on Meshtastic PR #8967 and LilyGO documentation + +#pragma once + +// I2C for OLED display (SH1106 at 0x3C) +#define I2C_SDA 8 +#define I2C_SCL 9 + +// GPS - Quectel L76K +// GNSS_TXD (IO5) = GPS transmits → MCU RX (setPins rxPin) +// GNSS_RXD (IO6) = GPS receives → MCU TX (setPins txPin) +#define PIN_GPS_TX 5 // MCU receives from GPS TX +#define PIN_GPS_RX 6 // MCU transmits to GPS RX +#define PIN_GPS_PPS 7 // GPS PPS output +#define PIN_GPS_EN 16 // GPS wake-up/enable (GPS_EN_PIN in LilyGO code) +#define HAS_GPS 1 +#define GPS_BAUDRATE 9600 + +// Buttons +#define BUTTON_PIN 0 // BUTTON 1 (boot) +#define BUTTON_PIN_ALT 17 // BUTTON 2 + +// SPI (shared by LoRa and SD) +#define SPI_MOSI 11 +#define SPI_SCK 13 +#define SPI_MISO 12 +#define SPI_CS 10 + +// SD Card +#define HAS_SDCARD +#define SDCARD_USE_SPI1 +#define SDCARD_CS SPI_CS + +// LoRa Radio - SX1262 with 1W PA +#define USE_SX1262 + +#define LORA_SCK SPI_SCK +#define LORA_MISO SPI_MISO +#define LORA_MOSI SPI_MOSI +#define LORA_CS 15 +#define LORA_RESET 3 +#define LORA_DIO1 1 +#define LORA_BUSY 38 + +// CRITICAL: Radio power enable - MUST be HIGH before lora.begin()! +// GPIO 40 powers the SX1262 + PA module via LDO +#define SX126X_POWER_EN 40 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_BUSY +#define SX126X_RESET LORA_RESET + +// RF switching configuration for 1W PA module +// DIO2 controls PA (via SX126X_DIO2_AS_RF_SWITCH) +// CTRL PIN (GPIO 21) controls LNA - must be HIGH during RX +// Truth table: DIO2=1,CTRL=0 -> TX (PA on, LNA off) +// DIO2=0,CTRL=1 -> RX (PA off, LNA on) +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_RXEN 21 // LNA enable - HIGH during RX + +// TCXO voltage - required for radio init +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define SX126X_MAX_POWER 22 +#endif + +// LED +#define LED_PIN 18 +#define LED_STATE_ON 1 // HIGH = ON + +// Battery ADC +#define BATTERY_PIN 4 +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define BATTERY_SENSE_SAMPLES 30 +#define ADC_MULTIPLIER 3.0 + +// NTC temperature sensor +#define NTC_PIN 14 + +// Fan control +#define FAN_CTRL_PIN 41 + +// PA Ramp Time - T-Beam 1W requires >800us stabilization (default is 200us) +// Value 0x05 = RADIOLIB_SX126X_PA_RAMP_800U +#define SX126X_PA_RAMP_US 0x05 + +// Display - SH1106 OLED (128x64) +#define USE_SH1106 +#define OLED_WIDTH 128 +#define OLED_HEIGHT 64 + +// 32768 Hz crystal present +#define HAS_32768HZ 1 From 44e7c092c8bef63eede999481ede41f4c728a737 Mon Sep 17 00:00:00 2001 From: Steven Linn Date: Wed, 28 Jan 2026 14:23:36 -0700 Subject: [PATCH 298/409] Add battery min/max voltage parameter support --- examples/companion_radio/ui-new/UITask.cpp | 10 ++++++++-- examples/companion_radio/ui-orig/UITask.cpp | 10 ++++++++-- variants/lilygo_tbeam_1w/platformio.ini | 4 ++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 8077627f8b..0690b45ac6 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -103,8 +103,14 @@ class HomeScreen : public UIScreen { void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) { // Convert millivolts to percentage - const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) - const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V) +#ifndef BATT_MIN_MILLIVOLTS + #define BATT_MIN_MILLIVOLTS 3000 +#endif +#ifndef BATT_MAX_MILLIVOLTS + #define BATT_MAX_MILLIVOLTS 4200 +#endif + const int minMilliVolts = BATT_MIN_MILLIVOLTS; + const int maxMilliVolts = BATT_MAX_MILLIVOLTS; int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts); if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0% if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100% diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 39cbf23ad5..3ad36fb000 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -149,8 +149,14 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i void UITask::renderBatteryIndicator(uint16_t batteryMilliVolts) { // Convert millivolts to percentage - const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) - const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V) +#ifndef BATT_MIN_MILLIVOLTS + #define BATT_MIN_MILLIVOLTS 3000 +#endif +#ifndef BATT_MAX_MILLIVOLTS + #define BATT_MAX_MILLIVOLTS 4200 +#endif + const int minMilliVolts = BATT_MIN_MILLIVOLTS; + const int maxMilliVolts = BATT_MAX_MILLIVOLTS; int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts); if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0% if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100% diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index 4b72b5e730..618a32a872 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -31,6 +31,10 @@ build_flags = ; TX power: 22dBm to SX1262, PA module adds ~10dB for 32dBm total -D LORA_TX_POWER=22 + ; Battery - 2S 7.4V LiPo (6.0V min, 8.4V max) + -D BATT_MIN_MILLIVOLTS=6000 + -D BATT_MAX_MILLIVOLTS=8400 + ; Display - SH1106 OLED at 0x3C -D DISPLAY_CLASS=SH1106Display From a9a8299e14172769dcc304d6d64fe203acaa01ce Mon Sep 17 00:00:00 2001 From: Steven Linn Date: Wed, 28 Jan 2026 14:24:53 -0700 Subject: [PATCH 299/409] Set LilyGO T-Beam 1W to use TX0 3.0V (within reference +2.85V~+3.15V) --- variants/lilygo_tbeam_1w/platformio.ini | 2 +- variants/lilygo_tbeam_1w/variant.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index 618a32a872..cf17ae8bd0 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -24,7 +24,7 @@ build_flags = ; Truth table: DIO2=1,RXEN=0 → TX | DIO2=0,RXEN=1 → RX -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_RXEN=21 - -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_DIO3_TCXO_VOLTAGE=3.0 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 diff --git a/variants/lilygo_tbeam_1w/variant.h b/variants/lilygo_tbeam_1w/variant.h index c05b16966e..f6807e56b4 100644 --- a/variants/lilygo_tbeam_1w/variant.h +++ b/variants/lilygo_tbeam_1w/variant.h @@ -62,7 +62,7 @@ #define SX126X_RXEN 21 // LNA enable - HIGH during RX // TCXO voltage - required for radio init -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 #define SX126X_MAX_POWER 22 #endif From 465776d66758e9608db1402b747e841d37b205b7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 29 Jan 2026 21:12:31 +1100 Subject: [PATCH 300/409] * ver 1.12.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index a2b0033f0a..95265a19ac 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "30 Nov 2025" +#define FIRMWARE_BUILD_DATE "29 Jan 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.11.0" +#define FIRMWARE_VERSION "v1.12.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 60d229021c..0d5cd28a3d 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -69,11 +69,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "30 Nov 2025" + #define FIRMWARE_BUILD_DATE "29 Jan 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.11.0" + #define FIRMWARE_VERSION "v1.12.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 4f3ed0e45c..f470e55eb8 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "30 Nov 2025" + #define FIRMWARE_BUILD_DATE "29 Jan 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.11.0" + #define FIRMWARE_VERSION "v1.12.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index eb2d90c5a0..ed35234582 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "30 Nov 2025" + #define FIRMWARE_BUILD_DATE "29 Jan 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.11.0" + #define FIRMWARE_VERSION "v1.12.0" #endif #define FIRMWARE_ROLE "sensor" From 3a7ccc085d5dab694a0eeeb61b5bf341f56ab958 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Thu, 29 Jan 2026 15:32:51 +0100 Subject: [PATCH 301/409] fixed build errors and typos/inconsistencies --- variants/thinknode_m3/ThinknodeM3Board.cpp | 22 +++++++++--- variants/thinknode_m3/ThinknodeM3Board.h | 42 ++++++++++------------ variants/thinknode_m3/target.cpp | 16 ++++----- variants/thinknode_m3/target.h | 4 +-- variants/thinknode_m6/ThinkNodeM6Board.h | 13 ++++--- 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinknodeM3Board.cpp index d7ecd62d57..ac513ade5b 100644 --- a/variants/thinknode_m3/ThinknodeM3Board.cpp +++ b/variants/thinknode_m3/ThinknodeM3Board.cpp @@ -1,14 +1,28 @@ #include -#include "ThinknodeM3Board.h" +#include "ThinkNodeM3Board.h" #include #include -void ThinknodeM3Board::begin() { - Nrf52BoardDCDC::begin(); +void ThinkNodeM3Board::begin() { + NRF52Board::begin(); btn_prev_state = HIGH; Wire.begin(); delay(10); // give sx1262 some time to power up -} \ No newline at end of file +} + +uint16_t ThinkNodeM3Board::getBattMilliVolts() { + 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); +} diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinknodeM3Board.h index 6269408758..1435d31d47 100644 --- a/variants/thinknode_m3/ThinknodeM3Board.h +++ b/variants/thinknode_m3/ThinknodeM3Board.h @@ -6,38 +6,26 @@ #define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX) -class ThinknodeM3Board : public Nrf52BoardDCDC { +class ThinkNodeM3Board : public NRF52BoardDCDC { protected: +#if NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif uint8_t btn_prev_state; public: + ThinkNodeM3Board() : NRF52Board("THINKNODE_M3_OTA") {} 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); - } + uint16_t getBattMilliVolts() override; #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 + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on } void onAfterTransmit() override { - digitalWrite(P_LORA_TX_LED, !P_LORA_TX_LED_ON); // turn TX LED off + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off } - #endif +#endif const char* getManufacturerName() const override { return "Elecrow ThinkNode M3"; @@ -54,5 +42,13 @@ class ThinknodeM3Board : public Nrf52BoardDCDC { return 0; } - void powerOff() override { sd_power_system_off(); } -}; \ No newline at end of file + void powerOff() override { + // turn off all leds, sd_power_system_off will not do this for us + #ifdef P_LORA_TX_LED + digitalWrite(P_LORA_TX_LED, LOW); + #endif + + // power off board + sd_power_system_off(); + } +}; diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp index c6708e4d13..91d186dc1b 100644 --- a/variants/thinknode_m3/target.cpp +++ b/variants/thinknode_m3/target.cpp @@ -2,7 +2,7 @@ #include "target.h" #include -ThinknodeM3Board board; +ThinkNodeM3Board board; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); @@ -30,26 +30,26 @@ static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, - RADIOLIB_NC, + RADIOLIB_NC, RADIOLIB_NC }; static const Module::RfSwitchMode_t rfswitch_table[] = { - // mode DIO5 DIO6 - { LR11x0::MODE_STBY, {LOW , LOW }}, + // 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_TX_HF, {LOW , LOW }}, { LR11x0::MODE_GNSS, {LOW , LOW }}, - { LR11x0::MODE_WIFI, {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 @@ -64,7 +64,7 @@ bool radio_init() { Serial.println(status); return false; // fail } - + radio.setCRC(2); radio.explicitHeader(); diff --git a/variants/thinknode_m3/target.h b/variants/thinknode_m3/target.h index f60a85b032..23e9958146 100644 --- a/variants/thinknode_m3/target.h +++ b/variants/thinknode_m3/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include "ThinknodeM3Board.h" +#include "ThinkNodeM3Board.h" #include #include #include @@ -17,7 +17,7 @@ extern NullDisplayDriver display; #endif -extern ThinknodeM3Board board; +extern ThinkNodeM3Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern EnvironmentSensorManager sensors; diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h index c03e1fbc60..32baa2a0a2 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.h +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -12,9 +12,14 @@ #define PIN_VBAT_READ BATTERY_PIN #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM6Board : public Nrf52BoardOTA { +class ThinkNodeM6Board : public NRF52BoardDCDC { +protected: +#if NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: - ThinkNodeM6Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {} + ThinkNodeM6Board() : NRF52Board("THINKNODE_M6_OTA") {} void begin(); uint16_t getBattMilliVolts() override; @@ -25,10 +30,10 @@ class ThinkNodeM6Board : public Nrf52BoardOTA { void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off } - #endif +#endif const char* getManufacturerName() const override { - return "Elecrow ThinkNode-M6"; + return "Elecrow ThinkNode M6"; } void powerOff() override { From 2a321b53ebfeea2d0b2a9160f944564633b8f9aa Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Thu, 29 Jan 2026 16:00:19 +0100 Subject: [PATCH 302/409] renamed board files --- .../thinknode_m3/{ThinknodeM3Board.cpp => ThinkNodeM3Board.cpp} | 0 variants/thinknode_m3/{ThinknodeM3Board.h => ThinkNodeM3Board.h} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename variants/thinknode_m3/{ThinknodeM3Board.cpp => ThinkNodeM3Board.cpp} (100%) rename variants/thinknode_m3/{ThinknodeM3Board.h => ThinkNodeM3Board.h} (100%) diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinkNodeM3Board.cpp similarity index 100% rename from variants/thinknode_m3/ThinknodeM3Board.cpp rename to variants/thinknode_m3/ThinkNodeM3Board.cpp diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinkNodeM3Board.h similarity index 100% rename from variants/thinknode_m3/ThinknodeM3Board.h rename to variants/thinknode_m3/ThinkNodeM3Board.h From c345f1da8e201bcac4aec02a56529192d15000ec Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Fri, 30 Jan 2026 00:12:04 +0100 Subject: [PATCH 303/409] Revert "Remove _serial->isConnected() logic from buzzer notifications" --- examples/companion_radio/MyMesh.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 2dad7866af..e0537707a2 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -330,10 +330,11 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); } - } + } else { #ifdef DISPLAY_CLASS - if (_ui && !_prefs.buzzer_quiet) _ui->notify(UIEventType::newContactMessage); //buzz if enabled + if (_ui) _ui->notify(UIEventType::newContactMessage); #endif + } // add inbound-path to mem cache if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid @@ -440,7 +441,9 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN; if (should_display && _ui) { _ui->newMsg(path_len, from.name, text, offline_queue_len); - if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::contactMessage); //buzz if enabled + if (!_serial->isConnected()) { + _ui->notify(UIEventType::contactMessage); + } } #endif } @@ -525,8 +528,11 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe uint8_t frame[1]; frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle' _serial->writeFrame(frame, 1); + } else { +#ifdef DISPLAY_CLASS + if (_ui) _ui->notify(UIEventType::channelMessage); +#endif } - #ifdef DISPLAY_CLASS // Get the channel name from the channel index const char *channel_name = "Unknown"; @@ -534,10 +540,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe if (getChannel(channel_idx, channel_details)) { channel_name = channel_details.name; } - if (_ui) { - _ui->newMsg(path_len, channel_name, text, offline_queue_len); - if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::channelMessage); //buzz if enabled - } + if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len); #endif } @@ -796,7 +799,6 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; - _prefs.buzzer_quiet = 0; _prefs.gps_enabled = 0; // GPS disabled by default _prefs.gps_interval = 0; // No automatic GPS updates by default //_prefs.rx_delay_base = 10.0f; enable once new algo fixed @@ -836,7 +838,6 @@ void MyMesh::begin(bool has_display) { _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); - _prefs.buzzer_quiet = constrain(_prefs.buzzer_quiet, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours From c7eea3915d5d23e8cbe5271d3ae4ccc11f4bc68b Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 30 Jan 2026 15:07:40 +1100 Subject: [PATCH 304/409] fix: remove esp_wifi.h from esp32board.h saves ~500 bytes of dram and allows Tbeam to compile again --- examples/companion_radio/main.cpp | 1 + src/helpers/ESP32Board.cpp | 1 + src/helpers/ESP32Board.h | 12 ++++++------ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7e636acee5..eff9efca47 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -194,6 +194,7 @@ void setup() { ); #ifdef WIFI_SSID + board.setInhibitSleep(true); // prevent sleep when WiFi is active WiFi.begin(WIFI_SSID, WIFI_PWD); serial_interface.begin(TCP_PORT); #elif defined(BLE_PIN_CODE) diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp index 4dce467cdd..e0ca1d0eeb 100644 --- a/src/helpers/ESP32Board.cpp +++ b/src/helpers/ESP32Board.cpp @@ -11,6 +11,7 @@ #include bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { + inhibit_sleep = true; // prevent sleep during OTA WiFi.softAP("MeshCore-OTA", NULL); sprintf(reply, "Started: http://%s/update", WiFi.softAPIP().toString().c_str()); diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 01b4c980cb..bade3e8980 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -8,12 +8,12 @@ #include #include #include -#include "esp_wifi.h" #include "driver/rtc_io.h" class ESP32Board : public mesh::MainBoard { protected: uint8_t startup_reason; + bool inhibit_sleep = false; public: void begin() { @@ -72,11 +72,7 @@ class ESP32Board : public mesh::MainBoard { } void sleep(uint32_t secs) override { - // 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 + if (!inhibit_sleep) { enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet } } @@ -126,6 +122,10 @@ class ESP32Board : public mesh::MainBoard { } bool startOTAUpdate(const char* id, char reply[]) override; + + void setInhibitSleep(bool inhibit) { + inhibit_sleep = inhibit; + } }; class ESP32RTCClock : public mesh::RTCClock { From 019bbf74d336995653aa97cb15d2d5a7b3d612e9 Mon Sep 17 00:00:00 2001 From: agessaman Date: Thu, 29 Jan 2026 20:44:11 -0800 Subject: [PATCH 305/409] Add recv_errors to CMD_GET_STATS STATS_TYPE_PACKETS response Append uint32_t recv_errors (RadioLib receive/CRC errors) to packet stats binary frame. Frame size 26 -> 30 bytes. Update stats_binary_frames.md and Python/TypeScript parsing examples for backward compatibility (accept >=26). --- docs/stats_binary_frames.md | 26 +++++++++++++++++++++----- examples/companion_radio/MyMesh.cpp | 2 ++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md index 1b409912ee..f3b17da96b 100644 --- a/docs/stats_binary_frames.md +++ b/docs/stats_binary_frames.md @@ -94,7 +94,7 @@ struct StatsRadio { ## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2) -**Total Frame Size:** 26 bytes +**Total Frame Size:** 26 bytes (legacy) or 30 bytes (includes `recv_errors`) | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| @@ -106,12 +106,14 @@ struct StatsRadio { | 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | | 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | | 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | +| 26 | 4 | uint32_t | recv_errors | Receive/CRC errors (RadioLib); present only in 30-byte frame | 0 - 4,294,967,295 | ### Notes - Counters are cumulative from boot and may wrap. - `recv = flood_rx + direct_rx` - `sent = flood_tx + direct_tx` +- Clients should accept frame length ≥ 26; if length ≥ 30, parse `recv_errors` at offset 26. ### Example Structure (C/C++) @@ -125,6 +127,7 @@ struct StatsPackets { uint32_t direct_tx; uint32_t flood_rx; uint32_t direct_rx; + uint32_t recv_errors; // present when frame size is 30 } __attribute__((packed)); ``` @@ -183,11 +186,12 @@ def parse_stats_radio(frame): } def parse_stats_packets(frame): - """Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)""" + """Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 or 30 bytes)""" + assert len(frame) >= 26, "STATS_TYPE_PACKETS frame too short" response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \ - struct.unpack('= 30: + (recv_errors,) = struct.unpack('= 30) { + result.recv_errors = view.getUint32(26, true); + } + return result; } ``` diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 2dad7866af..cfe3b77d78 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1688,12 +1688,14 @@ void MyMesh::handleCmdFrame(size_t len) { uint32_t n_sent_direct = getNumSentDirect(); uint32_t n_recv_flood = getNumRecvFlood(); uint32_t n_recv_direct = getNumRecvDirect(); + uint32_t n_recv_errors = radio_driver.getPacketsRecvErrors(); memcpy(&out_frame[i], &recv, 4); i += 4; memcpy(&out_frame[i], &sent, 4); i += 4; memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; + memcpy(&out_frame[i], &n_recv_errors, 4); i += 4; _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type From c786cfe613b37506dc48caac9273171d8a4df5b1 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 31 Jan 2026 10:22:32 +0100 Subject: [PATCH 306/409] Add KISS Modem firmware --- README.md | 2 + docs/kiss_modem_protocol.md | 110 +++++++++ examples/kiss_modem/KissModem.cpp | 362 ++++++++++++++++++++++++++++++ examples/kiss_modem/KissModem.h | 124 ++++++++++ examples/kiss_modem/main.cpp | 108 +++++++++ 5 files changed, 706 insertions(+) create mode 100644 docs/kiss_modem_protocol.md create mode 100644 examples/kiss_modem/KissModem.cpp create mode 100644 examples/kiss_modem/KissModem.h create mode 100644 examples/kiss_modem/main.cpp diff --git a/README.md b/README.md index d3bcbbef9d..9d47bffe3b 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,11 @@ For developers; - Clone and open the MeshCore repository in Visual Studio Code. - See the example applications you can modify and run: - [Companion Radio](./examples/companion_radio) - For use with an external chat app, over BLE, USB or WiFi. + - [KISS Modem](./examples/kiss_modem) - Serial KISS protocol bridge for host applications. ([protocol docs](./docs/kiss_modem_protocol.md)) - [Simple Repeater](./examples/simple_repeater) - Extends network coverage by relaying messages. - [Simple Room Server](./examples/simple_room_server) - A simple BBS server for shared Posts. - [Simple Secure Chat](./examples/simple_secure_chat) - Secure terminal based text communication between devices. + - [Simple Sensor](./examples/simple_sensor) - Remote sensor node with telemetry and alerting. The Simple Secure Chat example can be interacted with through the Serial Monitor in Visual Studio Code, or with a Serial USB Terminal on Android. diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md new file mode 100644 index 0000000000..f85bfe6c1a --- /dev/null +++ b/docs/kiss_modem_protocol.md @@ -0,0 +1,110 @@ +# MeshCore KISS Modem Protocol + +Serial protocol for the KISS modem firmware. Enables sending/receiving MeshCore packets over LoRa and cryptographic operations using the modem's identity. + +## Serial Configuration + +115200 baud, 8N1, no flow control. + +## Frame Format + +Standard KISS framing with byte stuffing. + +| Byte | Name | Description | +|------|------|-------------| +| `0xC0` | FEND | Frame delimiter | +| `0xDB` | FESC | Escape character | +| `0xDC` | TFEND | Escaped FEND (FESC + TFEND = 0xC0) | +| `0xDD` | TFESC | Escaped FESC (FESC + TFESC = 0xDB) | + +``` +┌──────┬─────────┬──────────────┬──────┐ +│ FEND │ Command │ Data (escaped)│ FEND │ +│ 0xC0 │ 1 byte │ 0-510 bytes │ 0xC0 │ +└──────┴─────────┴──────────────┴──────┘ +``` + +Maximum unescaped frame size: 512 bytes. + +## Commands + +### Request Commands (Host → Modem) + +| Command | Value | Data | +|---------|-------|------| +| `CMD_DATA` | `0x00` | Packet (2-255 bytes) | +| `CMD_GET_IDENTITY` | `0x01` | - | +| `CMD_GET_RANDOM` | `0x02` | Length (1 byte, 1-64) | +| `CMD_VERIFY_SIGNATURE` | `0x03` | PubKey (32) + Signature (64) + Data | +| `CMD_SIGN_DATA` | `0x04` | Data to sign | +| `CMD_ENCRYPT_DATA` | `0x05` | Key (32) + Plaintext | +| `CMD_DECRYPT_DATA` | `0x06` | Key (32) + MAC (2) + Ciphertext | +| `CMD_KEY_EXCHANGE` | `0x07` | Remote PubKey (32) | +| `CMD_HASH` | `0x08` | Data to hash | +| `CMD_SET_RADIO` | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | +| `CMD_SET_TX_POWER` | `0x0A` | Power dBm (1) | +| `CMD_SET_SYNC_WORD` | `0x0B` | Sync word (1) | +| `CMD_GET_RADIO` | `0x0C` | - | +| `CMD_GET_TX_POWER` | `0x0D` | - | +| `CMD_GET_SYNC_WORD` | `0x0E` | - | +| `CMD_GET_VERSION` | `0x0F` | - | + +### Response Commands (Modem → Host) + +| Command | Value | Data | +|---------|-------|------| +| `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet | +| `RESP_IDENTITY` | `0x11` | PubKey (32) | +| `RESP_RANDOM` | `0x12` | Random bytes (1-64) | +| `RESP_VERIFY` | `0x13` | Result (1): 0x00=invalid, 0x01=valid | +| `RESP_SIGNATURE` | `0x14` | Signature (64) | +| `RESP_ENCRYPTED` | `0x15` | MAC (2) + Ciphertext | +| `RESP_DECRYPTED` | `0x16` | Plaintext | +| `RESP_SHARED_SECRET` | `0x17` | Shared secret (32) | +| `RESP_HASH` | `0x18` | SHA-256 hash (32) | +| `RESP_OK` | `0x19` | - | +| `RESP_RADIO` | `0x1A` | Freq (4) + BW (4) + SF (1) + CR (1) | +| `RESP_TX_POWER` | `0x1B` | Power dBm (1) | +| `RESP_SYNC_WORD` | `0x1C` | Sync word (1) | +| `RESP_VERSION` | `0x1D` | Version (1) + Reserved (1) | +| `RESP_ERROR` | `0x1E` | Error code (1) | +| `RESP_TX_DONE` | `0x1F` | Result (1): 0x00=failed, 0x01=success | + +## Error Codes + +| Code | Value | Description | +|------|-------|-------------| +| `ERR_INVALID_LENGTH` | `0x01` | Request data too short | +| `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value | +| `ERR_NO_CALLBACK` | `0x03` | Radio callback not set | +| `ERR_MAC_FAILED` | `0x04` | MAC verification failed | +| `ERR_UNKNOWN_CMD` | `0x05` | Unknown command | +| `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed | + +## Data Formats + +### Radio Parameters (CMD_SET_RADIO / RESP_RADIO) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Frequency | 4 bytes | Hz (e.g., 869618000) | +| Bandwidth | 4 bytes | Hz (e.g., 62500) | +| SF | 1 byte | Spreading factor (5-12) | +| CR | 1 byte | Coding rate (5-8) | + +### Received Packet (CMD_DATA response) + +| Field | Size | Description | +|-------|------|-------------| +| SNR | 1 byte | Signal-to-noise × 4, signed | +| RSSI | 1 byte | Signal strength dBm, signed | +| Packet | variable | Raw MeshCore packet | + +## Notes + +- Modem generates identity on first boot (stored in flash) +- SNR values multiplied by 4 for 0.25 dB precision +- Wait for `RESP_TX_DONE` before sending next packet +- See [packet_structure.md](./packet_structure.md) for packet format diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp new file mode 100644 index 0000000000..4e227d7f7b --- /dev/null +++ b/examples/kiss_modem/KissModem.cpp @@ -0,0 +1,362 @@ +#include "KissModem.h" + +KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng) + : _serial(serial), _identity(identity), _rng(rng) { + _rx_len = 0; + _rx_escaped = false; + _rx_active = false; + _has_pending_tx = false; + _pending_tx_len = 0; + _setRadioCallback = nullptr; + _setTxPowerCallback = nullptr; + _setSyncWordCallback = nullptr; + _config = {0, 0, 0, 0, 0, 0x12}; +} + +void KissModem::begin() { + _rx_len = 0; + _rx_escaped = false; + _rx_active = false; + _has_pending_tx = false; +} + +void KissModem::writeByte(uint8_t b) { + if (b == KISS_FEND) { + _serial.write(KISS_FESC); + _serial.write(KISS_TFEND); + } else if (b == KISS_FESC) { + _serial.write(KISS_FESC); + _serial.write(KISS_TFESC); + } else { + _serial.write(b); + } +} + +void KissModem::writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len) { + _serial.write(KISS_FEND); + writeByte(cmd); + for (uint16_t i = 0; i < len; i++) { + writeByte(data[i]); + } + _serial.write(KISS_FEND); +} + +void KissModem::writeErrorFrame(uint8_t error_code) { + writeFrame(RESP_ERROR, &error_code, 1); +} + +void KissModem::loop() { + while (_serial.available()) { + uint8_t b = _serial.read(); + + if (b == KISS_FEND) { + if (_rx_active && _rx_len > 0) { + processFrame(); + } + _rx_len = 0; + _rx_escaped = false; + _rx_active = true; + continue; + } + + if (!_rx_active) continue; + + if (b == KISS_FESC) { + _rx_escaped = true; + continue; + } + + if (_rx_escaped) { + _rx_escaped = false; + if (b == KISS_TFEND) b = KISS_FEND; + else if (b == KISS_TFESC) b = KISS_FESC; + } + + if (_rx_len < KISS_MAX_FRAME_SIZE) { + _rx_buf[_rx_len++] = b; + } + } +} + +void KissModem::processFrame() { + if (_rx_len < 1) return; + + uint8_t cmd = _rx_buf[0]; + const uint8_t* data = &_rx_buf[1]; + uint16_t data_len = _rx_len - 1; + + switch (cmd) { + case CMD_DATA: + if (data_len < 2) { + writeErrorFrame(ERR_INVALID_LENGTH); + } else if (data_len > KISS_MAX_PACKET_SIZE) { + writeErrorFrame(ERR_INVALID_LENGTH); + } else { + memcpy(_pending_tx, data, data_len); + _pending_tx_len = data_len; + _has_pending_tx = true; + } + break; + case CMD_GET_IDENTITY: + handleGetIdentity(); + break; + case CMD_GET_RANDOM: + handleGetRandom(data, data_len); + break; + case CMD_VERIFY_SIGNATURE: + handleVerifySignature(data, data_len); + break; + case CMD_SIGN_DATA: + handleSignData(data, data_len); + break; + case CMD_ENCRYPT_DATA: + handleEncryptData(data, data_len); + break; + case CMD_DECRYPT_DATA: + handleDecryptData(data, data_len); + break; + case CMD_KEY_EXCHANGE: + handleKeyExchange(data, data_len); + break; + case CMD_HASH: + handleHash(data, data_len); + break; + case CMD_SET_RADIO: + handleSetRadio(data, data_len); + break; + case CMD_SET_TX_POWER: + handleSetTxPower(data, data_len); + break; + case CMD_SET_SYNC_WORD: + handleSetSyncWord(data, data_len); + break; + case CMD_GET_RADIO: + handleGetRadio(); + break; + case CMD_GET_TX_POWER: + handleGetTxPower(); + break; + case CMD_GET_SYNC_WORD: + handleGetSyncWord(); + break; + case CMD_GET_VERSION: + handleGetVersion(); + break; + default: + writeErrorFrame(ERR_UNKNOWN_CMD); + break; + } +} + +void KissModem::handleGetIdentity() { + writeFrame(RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); +} + +void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + uint8_t requested = data[0]; + if (requested < 1 || requested > 64) { + writeErrorFrame(ERR_INVALID_PARAM); + return; + } + + uint8_t buf[64]; + _rng.random(buf, requested); + writeFrame(RESP_RANDOM, buf, requested); +} + +void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { + if (len < PUB_KEY_SIZE + SIGNATURE_SIZE + 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + mesh::Identity signer(data); + const uint8_t* signature = data + PUB_KEY_SIZE; + const uint8_t* msg = data + PUB_KEY_SIZE + SIGNATURE_SIZE; + uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE; + + uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00; + writeFrame(RESP_VERIFY, &result, 1); +} + +void KissModem::handleSignData(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + uint8_t signature[SIGNATURE_SIZE]; + _identity.sign(signature, data, len); + writeFrame(RESP_SIGNATURE, signature, SIGNATURE_SIZE); +} + +void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { + if (len < PUB_KEY_SIZE + 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + const uint8_t* key = data; + const uint8_t* plaintext = data + PUB_KEY_SIZE; + uint16_t plaintext_len = len - PUB_KEY_SIZE; + + uint8_t buf[KISS_MAX_FRAME_SIZE]; + int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len); + + if (encrypted_len > 0) { + writeFrame(RESP_ENCRYPTED, buf, encrypted_len); + } else { + writeErrorFrame(ERR_ENCRYPT_FAILED); + } +} + +void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) { + if (len < PUB_KEY_SIZE + CIPHER_MAC_SIZE + 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + const uint8_t* key = data; + const uint8_t* ciphertext = data + PUB_KEY_SIZE; + uint16_t ciphertext_len = len - PUB_KEY_SIZE; + + uint8_t buf[KISS_MAX_FRAME_SIZE]; + int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len); + + if (decrypted_len > 0) { + writeFrame(RESP_DECRYPTED, buf, decrypted_len); + } else { + writeErrorFrame(ERR_MAC_FAILED); + } +} + +void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) { + if (len < PUB_KEY_SIZE) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + uint8_t shared_secret[PUB_KEY_SIZE]; + _identity.calcSharedSecret(shared_secret, data); + writeFrame(RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); +} + +void KissModem::handleHash(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + uint8_t hash[32]; + mesh::Utils::sha256(hash, 32, data, len); + writeFrame(RESP_HASH, hash, 32); +} + +bool KissModem::getPacketToSend(uint8_t* packet, uint16_t* len) { + if (!_has_pending_tx) return false; + + memcpy(packet, _pending_tx, _pending_tx_len); + *len = _pending_tx_len; + _has_pending_tx = false; + return true; +} + +void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { + uint8_t buf[2 + KISS_MAX_PACKET_SIZE]; + buf[0] = (uint8_t)snr; + buf[1] = (uint8_t)rssi; + memcpy(&buf[2], packet, len); + writeFrame(CMD_DATA, buf, 2 + len); +} + +void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { + if (len < 10) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_setRadioCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint32_t freq_hz, bw_hz; + memcpy(&freq_hz, data, 4); + memcpy(&bw_hz, data + 4, 4); + uint8_t sf = data[8]; + uint8_t cr = data[9]; + + _config.freq_hz = freq_hz; + _config.bw_hz = bw_hz; + _config.sf = sf; + _config.cr = cr; + + float freq = freq_hz / 1000000.0f; + float bw = bw_hz / 1000.0f; + + _setRadioCallback(freq, bw, sf, cr); + writeFrame(RESP_OK, nullptr, 0); +} + +void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_setTxPowerCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + _config.tx_power = data[0]; + _setTxPowerCallback(data[0]); + writeFrame(RESP_OK, nullptr, 0); +} + +void KissModem::handleSetSyncWord(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_setSyncWordCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + _config.sync_word = data[0]; + _setSyncWordCallback(data[0]); + writeFrame(RESP_OK, nullptr, 0); +} + +void KissModem::handleGetRadio() { + uint8_t buf[10]; + memcpy(buf, &_config.freq_hz, 4); + memcpy(buf + 4, &_config.bw_hz, 4); + buf[8] = _config.sf; + buf[9] = _config.cr; + writeFrame(RESP_RADIO, buf, 10); +} + +void KissModem::handleGetTxPower() { + writeFrame(RESP_TX_POWER, &_config.tx_power, 1); +} + +void KissModem::handleGetSyncWord() { + writeFrame(RESP_SYNC_WORD, &_config.sync_word, 1); +} + +void KissModem::handleGetVersion() { + uint8_t buf[2]; + buf[0] = KISS_FIRMWARE_VERSION; + buf[1] = 0; + writeFrame(RESP_VERSION, buf, 2); +} + +void KissModem::onTxComplete(bool success) { + uint8_t result = success ? 0x01 : 0x00; + writeFrame(RESP_TX_DONE, &result, 1); +} diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h new file mode 100644 index 0000000000..34d9577f59 --- /dev/null +++ b/examples/kiss_modem/KissModem.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include + +#define KISS_FEND 0xC0 +#define KISS_FESC 0xDB +#define KISS_TFEND 0xDC +#define KISS_TFESC 0xDD + +#define KISS_MAX_FRAME_SIZE 512 +#define KISS_MAX_PACKET_SIZE 255 + +#define CMD_DATA 0x00 +#define CMD_GET_IDENTITY 0x01 +#define CMD_GET_RANDOM 0x02 +#define CMD_VERIFY_SIGNATURE 0x03 +#define CMD_SIGN_DATA 0x04 +#define CMD_ENCRYPT_DATA 0x05 +#define CMD_DECRYPT_DATA 0x06 +#define CMD_KEY_EXCHANGE 0x07 +#define CMD_HASH 0x08 +#define CMD_SET_RADIO 0x09 +#define CMD_SET_TX_POWER 0x0A +#define CMD_SET_SYNC_WORD 0x0B +#define CMD_GET_RADIO 0x0C +#define CMD_GET_TX_POWER 0x0D +#define CMD_GET_SYNC_WORD 0x0E +#define CMD_GET_VERSION 0x0F + +#define RESP_IDENTITY 0x11 +#define RESP_RANDOM 0x12 +#define RESP_VERIFY 0x13 +#define RESP_SIGNATURE 0x14 +#define RESP_ENCRYPTED 0x15 +#define RESP_DECRYPTED 0x16 +#define RESP_SHARED_SECRET 0x17 +#define RESP_HASH 0x18 +#define RESP_OK 0x19 +#define RESP_RADIO 0x1A +#define RESP_TX_POWER 0x1B +#define RESP_SYNC_WORD 0x1C +#define RESP_VERSION 0x1D +#define RESP_ERROR 0x1E +#define RESP_TX_DONE 0x1F + +#define ERR_INVALID_LENGTH 0x01 +#define ERR_INVALID_PARAM 0x02 +#define ERR_NO_CALLBACK 0x03 +#define ERR_MAC_FAILED 0x04 +#define ERR_UNKNOWN_CMD 0x05 +#define ERR_ENCRYPT_FAILED 0x06 + +#define KISS_FIRMWARE_VERSION 1 + +typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); +typedef void (*SetTxPowerCallback)(uint8_t power); +typedef void (*SetSyncWordCallback)(uint8_t syncWord); + +struct RadioConfig { + uint32_t freq_hz; + uint32_t bw_hz; + uint8_t sf; + uint8_t cr; + uint8_t tx_power; + uint8_t sync_word; +}; + +class KissModem { + Stream& _serial; + mesh::LocalIdentity& _identity; + mesh::RNG& _rng; + + uint8_t _rx_buf[KISS_MAX_FRAME_SIZE]; + uint16_t _rx_len; + bool _rx_escaped; + bool _rx_active; + + uint8_t _pending_tx[KISS_MAX_PACKET_SIZE]; + uint16_t _pending_tx_len; + bool _has_pending_tx; + + SetRadioCallback _setRadioCallback; + SetTxPowerCallback _setTxPowerCallback; + SetSyncWordCallback _setSyncWordCallback; + + RadioConfig _config; + + void writeByte(uint8_t b); + void writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len); + void writeErrorFrame(uint8_t error_code); + void processFrame(); + + void handleGetIdentity(); + void handleGetRandom(const uint8_t* data, uint16_t len); + void handleVerifySignature(const uint8_t* data, uint16_t len); + void handleSignData(const uint8_t* data, uint16_t len); + void handleEncryptData(const uint8_t* data, uint16_t len); + void handleDecryptData(const uint8_t* data, uint16_t len); + void handleKeyExchange(const uint8_t* data, uint16_t len); + void handleHash(const uint8_t* data, uint16_t len); + void handleSetRadio(const uint8_t* data, uint16_t len); + void handleSetTxPower(const uint8_t* data, uint16_t len); + void handleSetSyncWord(const uint8_t* data, uint16_t len); + void handleGetRadio(); + void handleGetTxPower(); + void handleGetSyncWord(); + void handleGetVersion(); + +public: + KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng); + + void begin(); + void loop(); + + void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } + void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } + void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } + + bool getPacketToSend(uint8_t* packet, uint16_t* len); + void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); + void onTxComplete(bool success); +}; diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp new file mode 100644 index 0000000000..2f843a9911 --- /dev/null +++ b/examples/kiss_modem/main.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include "KissModem.h" + +#if defined(NRF52_PLATFORM) + #include +#elif defined(RP2040_PLATFORM) + #include +#elif defined(ESP32) + #include +#endif + +StdRNG rng; +mesh::LocalIdentity identity; +KissModem* modem; + +void halt() { + while (1) ; +} + +void loadOrCreateIdentity() { +#if defined(NRF52_PLATFORM) + InternalFS.begin(); + IdentityStore store(InternalFS, ""); +#elif defined(ESP32) + SPIFFS.begin(true); + IdentityStore store(SPIFFS, "/identity"); +#elif defined(RP2040_PLATFORM) + LittleFS.begin(); + IdentityStore store(LittleFS, "/identity"); + store.begin(); +#else + #error "Filesystem not defined" +#endif + + if (!store.load("_main", identity)) { + identity = radio_new_identity(); + while (identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF) { + identity = radio_new_identity(); + } + store.save("_main", identity); + } +} + +void onSetRadio(float freq, float bw, uint8_t sf, uint8_t cr) { + radio_set_params(freq, bw, sf, cr); +} + +void onSetTxPower(uint8_t power) { + radio_set_tx_power(power); +} + +void onSetSyncWord(uint8_t sync_word) { + radio_set_sync_word(sync_word); +} + +void setup() { + board.begin(); + + if (!radio_init()) { + halt(); + } + + radio_driver.begin(); + + rng.begin(radio_get_rng_seed()); + loadOrCreateIdentity(); + + Serial.begin(115200); + uint32_t start = millis(); + while (!Serial && millis() - start < 3000) delay(10); + delay(100); + + modem = new KissModem(Serial, identity, rng); + modem->setRadioCallback(onSetRadio); + modem->setTxPowerCallback(onSetTxPower); + modem->setSyncWordCallback(onSetSyncWord); + modem->begin(); +} + +void loop() { + modem->loop(); + + uint8_t packet[KISS_MAX_PACKET_SIZE]; + uint16_t len; + + if (modem->getPacketToSend(packet, &len)) { + radio_driver.startSendRaw(packet, len); + while (!radio_driver.isSendComplete()) { + delay(1); + } + radio_driver.onSendFinished(); + modem->onTxComplete(true); + } + + uint8_t rx_buf[256]; + int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); + + if (rx_len > 0) { + int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); + int8_t rssi = (int8_t)radio_driver.getLastRSSI(); + modem->onPacketReceived(snr, rssi, rx_buf, rx_len); + } + + radio_driver.loop(); +} From c5b1d30280c837214f18db2e19bfcae9151568a1 Mon Sep 17 00:00:00 2001 From: taco Date: Sat, 31 Jan 2026 23:48:28 +1100 Subject: [PATCH 307/409] t114: remove extra DCDC enable --- variants/heltec_t114/T114Board.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index 2a36bd9039..c03d39afb6 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -34,7 +34,6 @@ void T114Board::initiateShutdown(uint8_t reason) { void T114Board::begin() { NRF52Board::begin(); - NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT_READ, INPUT); From e6e1b810f874491b1e7cf96869f5076069ddc6fa Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 27 Jan 2026 17:51:30 +1100 Subject: [PATCH 308/409] add DataStore::deleteBlobByKey() --- examples/companion_radio/DataStore.cpp | 16 ++++++++++++++++ examples/companion_radio/DataStore.h | 1 + examples/companion_radio/MyMesh.cpp | 2 ++ 3 files changed, 19 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index f61f53aeed..6cc7767177 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -560,6 +560,9 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src } return false; // error } +bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { + return true; // this is just a stub on NRF52/STM32 platforms +} #else uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { char path[64]; @@ -598,4 +601,17 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src } return false; // error } + +bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { + char path[64]; + char fname[18]; + + if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) + mesh::Utils::toHex(fname, key, key_len); + sprintf(path, "/bl/%s", fname); + + _fs->remove(path); + + return true; // return true even if file did not exist +} #endif diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h index 6258094295..58b4d5d284 100644 --- a/examples/companion_radio/DataStore.h +++ b/examples/companion_radio/DataStore.h @@ -42,6 +42,7 @@ class DataStore { void migrateToSecondaryFS(); uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]); bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len); + bool deleteBlobByKey(const uint8_t key[], int key_len); File openRead(const char* filename); File openRead(FILESYSTEM* fs, const char* filename); bool removeFile(const char* filename); diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 1e4115dada..9bb747e791 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -307,6 +307,7 @@ bool MyMesh::shouldOverwriteWhenFull() const { } void MyMesh::onContactOverwrite(const uint8_t* pub_key) { + _store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); // delete from storage if (_serial->isConnected()) { out_frame[0] = PUSH_CODE_CONTACT_DELETED; memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE); @@ -1124,6 +1125,7 @@ void MyMesh::handleCmdFrame(size_t len) { uint8_t *pub_key = &cmd_frame[1]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); if (recipient && removeContact(*recipient)) { + _store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); } else { From 31ba971c60e7367de3265a0f006edacbc2ebbe4c Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 27 Jan 2026 17:53:05 +1100 Subject: [PATCH 309/409] only store advblob when adding/updating contacts --- src/helpers/BaseChatMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index aebfc1b644..6de7469d0d 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -131,7 +131,6 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, plen = packet->writeTo(temp_buf); packet->header = save; } - putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); bool is_new = false; // true = not in contacts[], false = exists in contacts[] if (from == NULL) { @@ -157,6 +156,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, from->shared_secret_valid = false; } // update + putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); from->type = parser.getType(); if (parser.hasLatLon()) { From 8d5eaf500d8e744d204339be078f10074213d9e2 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 27 Jan 2026 19:31:07 +1100 Subject: [PATCH 310/409] add makeBlobPath inline helper for esp32 --- examples/companion_radio/DataStore.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 6cc7767177..c0f2c0212c 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -564,13 +564,16 @@ bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { return true; // this is just a stub on NRF52/STM32 platforms } #else -uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { - char path[64]; +inline void makeBlobPath(const uint8_t key[], int key_len, char* path, size_t path_size) { char fname[18]; - if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) mesh::Utils::toHex(fname, key, key_len); sprintf(path, "/bl/%s", fname); +} + +uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { + char path[64]; + makeBlobPath(key, key_len, path, sizeof(path)); if (_fs->exists(path)) { File f = openRead(_fs, path); @@ -585,11 +588,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { char path[64]; - char fname[18]; - - if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) - mesh::Utils::toHex(fname, key, key_len); - sprintf(path, "/bl/%s", fname); + makeBlobPath(key, key_len, path, sizeof(path)); File f = openWrite(_fs, path); if (f) { @@ -604,11 +603,7 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { char path[64]; - char fname[18]; - - if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) - mesh::Utils::toHex(fname, key, key_len); - sprintf(path, "/bl/%s", fname); + makeBlobPath(key, key_len, path, sizeof(path)); _fs->remove(path); From b5248faec4872a52001ee1ed425492db043ee943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sat, 31 Jan 2026 13:45:58 +0000 Subject: [PATCH 311/409] Revert "Merge pull request #1428 from etienn01/update-t114-i2c" This reverts commit 616eb57b163f2123727347ab0425e1ad4fbca564, reversing changes made to 537acd7ea144ee077595c1171cd96770eb924b67. This patch needs to be reverted because it boot freezes t114 433Mhz variant. --- variants/heltec_t114/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index ac9dbbe6e4..aa7f40222b 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -58,8 +58,8 @@ //////////////////////////////////////////////////////////////////////////////// // I2C pin definition -#define PIN_WIRE_SDA (16) // P0.16 -#define PIN_WIRE_SCL (13) // P0.13 +#define PIN_WIRE_SDA (26) // P0.26 +#define PIN_WIRE_SCL (27) // P0.27 //////////////////////////////////////////////////////////////////////////////// // SPI pin definition From 1bcb52bab318926b014d0a46d98ebc2f35ff5e3f Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 31 Jan 2026 15:05:25 +0100 Subject: [PATCH 312/409] Add new commands and responses for RSSI, channel status, airtime, noise floor, statistics, battery, and sensors. --- docs/kiss_modem_protocol.md | 74 +++++++++++++---- examples/kiss_modem/KissModem.cpp | 128 ++++++++++++++++++++++++++++++ examples/kiss_modem/KissModem.h | 78 ++++++++++++++---- examples/kiss_modem/main.cpp | 46 +++++++++++ 4 files changed, 294 insertions(+), 32 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index f85bfe6c1a..e80c3b29ba 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -48,27 +48,43 @@ Maximum unescaped frame size: 512 bytes. | `CMD_GET_TX_POWER` | `0x0D` | - | | `CMD_GET_SYNC_WORD` | `0x0E` | - | | `CMD_GET_VERSION` | `0x0F` | - | +| `CMD_GET_CURRENT_RSSI` | `0x10` | - | +| `CMD_IS_CHANNEL_BUSY` | `0x11` | - | +| `CMD_GET_AIRTIME` | `0x12` | Packet length (1) | +| `CMD_GET_NOISE_FLOOR` | `0x13` | - | +| `CMD_GET_STATS` | `0x14` | - | +| `CMD_GET_BATTERY` | `0x15` | - | +| `CMD_PING` | `0x16` | - | +| `CMD_GET_SENSORS` | `0x17` | Permissions (1) | ### Response Commands (Modem → Host) | Command | Value | Data | |---------|-------|------| | `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet | -| `RESP_IDENTITY` | `0x11` | PubKey (32) | -| `RESP_RANDOM` | `0x12` | Random bytes (1-64) | -| `RESP_VERIFY` | `0x13` | Result (1): 0x00=invalid, 0x01=valid | -| `RESP_SIGNATURE` | `0x14` | Signature (64) | -| `RESP_ENCRYPTED` | `0x15` | MAC (2) + Ciphertext | -| `RESP_DECRYPTED` | `0x16` | Plaintext | -| `RESP_SHARED_SECRET` | `0x17` | Shared secret (32) | -| `RESP_HASH` | `0x18` | SHA-256 hash (32) | -| `RESP_OK` | `0x19` | - | -| `RESP_RADIO` | `0x1A` | Freq (4) + BW (4) + SF (1) + CR (1) | -| `RESP_TX_POWER` | `0x1B` | Power dBm (1) | -| `RESP_SYNC_WORD` | `0x1C` | Sync word (1) | -| `RESP_VERSION` | `0x1D` | Version (1) + Reserved (1) | -| `RESP_ERROR` | `0x1E` | Error code (1) | -| `RESP_TX_DONE` | `0x1F` | Result (1): 0x00=failed, 0x01=success | +| `RESP_IDENTITY` | `0x21` | PubKey (32) | +| `RESP_RANDOM` | `0x22` | Random bytes (1-64) | +| `RESP_VERIFY` | `0x23` | Result (1): 0x00=invalid, 0x01=valid | +| `RESP_SIGNATURE` | `0x24` | Signature (64) | +| `RESP_ENCRYPTED` | `0x25` | MAC (2) + Ciphertext | +| `RESP_DECRYPTED` | `0x26` | Plaintext | +| `RESP_SHARED_SECRET` | `0x27` | Shared secret (32) | +| `RESP_HASH` | `0x28` | SHA-256 hash (32) | +| `RESP_OK` | `0x29` | - | +| `RESP_RADIO` | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | +| `RESP_TX_POWER` | `0x2B` | Power dBm (1) | +| `RESP_SYNC_WORD` | `0x2C` | Sync word (1) | +| `RESP_VERSION` | `0x2D` | Version (1) + Reserved (1) | +| `RESP_ERROR` | `0x2E` | Error code (1) | +| `RESP_TX_DONE` | `0x2F` | Result (1): 0x00=failed, 0x01=success | +| `RESP_CURRENT_RSSI` | `0x30` | RSSI dBm (1, signed) | +| `RESP_CHANNEL_BUSY` | `0x31` | Result (1): 0x00=clear, 0x01=busy | +| `RESP_AIRTIME` | `0x32` | Milliseconds (4) | +| `RESP_NOISE_FLOOR` | `0x33` | dBm (2, signed) | +| `RESP_STATS` | `0x34` | RX (4) + TX (4) + Errors (4) | +| `RESP_BATTERY` | `0x35` | Millivolts (2) | +| `RESP_PONG` | `0x36` | - | +| `RESP_SENSORS` | `0x37` | CayenneLPP payload | ## Error Codes @@ -76,10 +92,11 @@ Maximum unescaped frame size: 512 bytes. |------|-------|-------------| | `ERR_INVALID_LENGTH` | `0x01` | Request data too short | | `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value | -| `ERR_NO_CALLBACK` | `0x03` | Radio callback not set | +| `ERR_NO_CALLBACK` | `0x03` | Feature not available | | `ERR_MAC_FAILED` | `0x04` | MAC verification failed | | `ERR_UNKNOWN_CMD` | `0x05` | Unknown command | | `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed | +| `ERR_TX_PENDING` | `0x07` | TX already pending | ## Data Formats @@ -102,9 +119,34 @@ All values little-endian. | RSSI | 1 byte | Signal strength dBm, signed | | Packet | variable | Raw MeshCore packet | +### Stats (RESP_STATS) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| RX | 4 bytes | Packets received | +| TX | 4 bytes | Packets transmitted | +| Errors | 4 bytes | Receive errors | + +### Sensor Permissions (CMD_GET_SENSORS) + +| Bit | Value | Description | +|-----|-------|-------------| +| 0 | `0x01` | Base (battery) | +| 1 | `0x02` | Location (GPS) | +| 2 | `0x04` | Environment (temp, humidity, pressure) | + +Use `0x07` for all permissions. + +### Sensor Data (RESP_SENSORS) + +Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) for parsing. + ## Notes - Modem generates identity on first boot (stored in flash) - SNR values multiplied by 4 for 0.25 dB precision - Wait for `RESP_TX_DONE` before sending next packet +- Sending `CMD_DATA` while TX is pending returns `ERR_TX_PENDING` - See [packet_structure.md](./packet_structure.md) for packet format diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 4e227d7f7b..c6e2f2bdcf 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -10,6 +10,13 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _setRadioCallback = nullptr; _setTxPowerCallback = nullptr; _setSyncWordCallback = nullptr; + _getCurrentRssiCallback = nullptr; + _isChannelBusyCallback = nullptr; + _getAirtimeCallback = nullptr; + _getNoiseFloorCallback = nullptr; + _getStatsCallback = nullptr; + _getBatteryCallback = nullptr; + _getSensorsCallback = nullptr; _config = {0, 0, 0, 0, 0, 0x12}; } @@ -91,6 +98,8 @@ void KissModem::processFrame() { writeErrorFrame(ERR_INVALID_LENGTH); } else if (data_len > KISS_MAX_PACKET_SIZE) { writeErrorFrame(ERR_INVALID_LENGTH); + } else if (_has_pending_tx) { + writeErrorFrame(ERR_TX_PENDING); } else { memcpy(_pending_tx, data, data_len); _pending_tx_len = data_len; @@ -142,6 +151,30 @@ void KissModem::processFrame() { case CMD_GET_VERSION: handleGetVersion(); break; + case CMD_GET_CURRENT_RSSI: + handleGetCurrentRssi(); + break; + case CMD_IS_CHANNEL_BUSY: + handleIsChannelBusy(); + break; + case CMD_GET_AIRTIME: + handleGetAirtime(data, data_len); + break; + case CMD_GET_NOISE_FLOOR: + handleGetNoiseFloor(); + break; + case CMD_GET_STATS: + handleGetStats(); + break; + case CMD_GET_BATTERY: + handleGetBattery(); + break; + case CMD_PING: + handlePing(); + break; + case CMD_GET_SENSORS: + handleGetSensors(data, data_len); + break; default: writeErrorFrame(ERR_UNKNOWN_CMD); break; @@ -360,3 +393,98 @@ void KissModem::onTxComplete(bool success) { uint8_t result = success ? 0x01 : 0x00; writeFrame(RESP_TX_DONE, &result, 1); } + +void KissModem::handleGetCurrentRssi() { + if (!_getCurrentRssiCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + float rssi = _getCurrentRssiCallback(); + int8_t rssi_byte = (int8_t)rssi; + writeFrame(RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); +} + +void KissModem::handleIsChannelBusy() { + if (!_isChannelBusyCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint8_t busy = _isChannelBusyCallback() ? 0x01 : 0x00; + writeFrame(RESP_CHANNEL_BUSY, &busy, 1); +} + +void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_getAirtimeCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint8_t packet_len = data[0]; + uint32_t airtime = _getAirtimeCallback(packet_len); + writeFrame(RESP_AIRTIME, (uint8_t*)&airtime, 4); +} + +void KissModem::handleGetNoiseFloor() { + if (!_getNoiseFloorCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + int16_t noise_floor = _getNoiseFloorCallback(); + writeFrame(RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); +} + +void KissModem::handleGetStats() { + if (!_getStatsCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint32_t rx, tx, errors; + _getStatsCallback(&rx, &tx, &errors); + uint8_t buf[12]; + memcpy(buf, &rx, 4); + memcpy(buf + 4, &tx, 4); + memcpy(buf + 8, &errors, 4); + writeFrame(RESP_STATS, buf, 12); +} + +void KissModem::handleGetBattery() { + if (!_getBatteryCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint16_t mv = _getBatteryCallback(); + writeFrame(RESP_BATTERY, (uint8_t*)&mv, 2); +} + +void KissModem::handlePing() { + writeFrame(RESP_PONG, nullptr, 0); +} + +void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_getSensorsCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint8_t permissions = data[0]; + uint8_t buf[255]; + uint8_t result_len = _getSensorsCallback(permissions, buf, 255); + if (result_len > 0) { + writeFrame(RESP_SENSORS, buf, result_len); + } else { + writeFrame(RESP_SENSORS, nullptr, 0); + } +} diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 34d9577f59..e223d92d1a 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -28,22 +28,38 @@ #define CMD_GET_TX_POWER 0x0D #define CMD_GET_SYNC_WORD 0x0E #define CMD_GET_VERSION 0x0F +#define CMD_GET_CURRENT_RSSI 0x10 +#define CMD_IS_CHANNEL_BUSY 0x11 +#define CMD_GET_AIRTIME 0x12 +#define CMD_GET_NOISE_FLOOR 0x13 +#define CMD_GET_STATS 0x14 +#define CMD_GET_BATTERY 0x15 +#define CMD_PING 0x16 +#define CMD_GET_SENSORS 0x17 -#define RESP_IDENTITY 0x11 -#define RESP_RANDOM 0x12 -#define RESP_VERIFY 0x13 -#define RESP_SIGNATURE 0x14 -#define RESP_ENCRYPTED 0x15 -#define RESP_DECRYPTED 0x16 -#define RESP_SHARED_SECRET 0x17 -#define RESP_HASH 0x18 -#define RESP_OK 0x19 -#define RESP_RADIO 0x1A -#define RESP_TX_POWER 0x1B -#define RESP_SYNC_WORD 0x1C -#define RESP_VERSION 0x1D -#define RESP_ERROR 0x1E -#define RESP_TX_DONE 0x1F +#define RESP_IDENTITY 0x21 +#define RESP_RANDOM 0x22 +#define RESP_VERIFY 0x23 +#define RESP_SIGNATURE 0x24 +#define RESP_ENCRYPTED 0x25 +#define RESP_DECRYPTED 0x26 +#define RESP_SHARED_SECRET 0x27 +#define RESP_HASH 0x28 +#define RESP_OK 0x29 +#define RESP_RADIO 0x2A +#define RESP_TX_POWER 0x2B +#define RESP_SYNC_WORD 0x2C +#define RESP_VERSION 0x2D +#define RESP_ERROR 0x2E +#define RESP_TX_DONE 0x2F +#define RESP_CURRENT_RSSI 0x30 +#define RESP_CHANNEL_BUSY 0x31 +#define RESP_AIRTIME 0x32 +#define RESP_NOISE_FLOOR 0x33 +#define RESP_STATS 0x34 +#define RESP_BATTERY 0x35 +#define RESP_PONG 0x36 +#define RESP_SENSORS 0x37 #define ERR_INVALID_LENGTH 0x01 #define ERR_INVALID_PARAM 0x02 @@ -51,12 +67,20 @@ #define ERR_MAC_FAILED 0x04 #define ERR_UNKNOWN_CMD 0x05 #define ERR_ENCRYPT_FAILED 0x06 +#define ERR_TX_PENDING 0x07 -#define KISS_FIRMWARE_VERSION 1 +#define KISS_FIRMWARE_VERSION 2 typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); typedef void (*SetSyncWordCallback)(uint8_t syncWord); +typedef float (*GetCurrentRssiCallback)(); +typedef bool (*IsChannelBusyCallback)(); +typedef uint32_t (*GetAirtimeCallback)(uint8_t len); +typedef int16_t (*GetNoiseFloorCallback)(); +typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); +typedef uint16_t (*GetBatteryCallback)(); +typedef uint8_t (*GetSensorsCallback)(uint8_t permissions, uint8_t* buffer, uint8_t max_len); struct RadioConfig { uint32_t freq_hz; @@ -84,6 +108,13 @@ class KissModem { SetRadioCallback _setRadioCallback; SetTxPowerCallback _setTxPowerCallback; SetSyncWordCallback _setSyncWordCallback; + GetCurrentRssiCallback _getCurrentRssiCallback; + IsChannelBusyCallback _isChannelBusyCallback; + GetAirtimeCallback _getAirtimeCallback; + GetNoiseFloorCallback _getNoiseFloorCallback; + GetStatsCallback _getStatsCallback; + GetBatteryCallback _getBatteryCallback; + GetSensorsCallback _getSensorsCallback; RadioConfig _config; @@ -107,6 +138,14 @@ class KissModem { void handleGetTxPower(); void handleGetSyncWord(); void handleGetVersion(); + void handleGetCurrentRssi(); + void handleIsChannelBusy(); + void handleGetAirtime(const uint8_t* data, uint16_t len); + void handleGetNoiseFloor(); + void handleGetStats(); + void handleGetBattery(); + void handlePing(); + void handleGetSensors(const uint8_t* data, uint16_t len); public: KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng); @@ -117,6 +156,13 @@ class KissModem { void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } + void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } + void setIsChannelBusyCallback(IsChannelBusyCallback cb) { _isChannelBusyCallback = cb; } + void setGetAirtimeCallback(GetAirtimeCallback cb) { _getAirtimeCallback = cb; } + void setGetNoiseFloorCallback(GetNoiseFloorCallback cb) { _getNoiseFloorCallback = cb; } + void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } + void setGetBatteryCallback(GetBatteryCallback cb) { _getBatteryCallback = cb; } + void setGetSensorsCallback(GetSensorsCallback cb) { _getSensorsCallback = cb; } bool getPacketToSend(uint8_t* packet, uint16_t* len); void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 2f843a9911..0a54c9d356 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "KissModem.h" #if defined(NRF52_PLATFORM) @@ -56,6 +57,42 @@ void onSetSyncWord(uint8_t sync_word) { radio_set_sync_word(sync_word); } +float onGetCurrentRssi() { + return radio_driver.getCurrentRSSI(); +} + +bool onIsChannelBusy() { + return radio_driver.isReceiving(); +} + +uint32_t onGetAirtime(uint8_t len) { + return radio_driver.getEstAirtimeFor(len); +} + +int16_t onGetNoiseFloor() { + return radio_driver.getNoiseFloor(); +} + +void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { + *rx = radio_driver.getPacketsRecv(); + *tx = radio_driver.getPacketsSent(); + *errors = radio_driver.getPacketsRecvErrors(); +} + +uint16_t onGetBattery() { + return board.getBattMilliVolts(); +} + +uint8_t onGetSensors(uint8_t permissions, uint8_t* buffer, uint8_t max_len) { + CayenneLPP telemetry(max_len); + if (sensors.querySensors(permissions, telemetry)) { + uint8_t len = telemetry.getSize(); + memcpy(buffer, telemetry.getBuffer(), len); + return len; + } + return 0; +} + void setup() { board.begin(); @@ -73,10 +110,19 @@ void setup() { while (!Serial && millis() - start < 3000) delay(10); delay(100); + sensors.begin(); + modem = new KissModem(Serial, identity, rng); modem->setRadioCallback(onSetRadio); modem->setTxPowerCallback(onSetTxPower); modem->setSyncWordCallback(onSetSyncWord); + modem->setGetCurrentRssiCallback(onGetCurrentRssi); + modem->setIsChannelBusyCallback(onIsChannelBusy); + modem->setGetAirtimeCallback(onGetAirtime); + modem->setGetNoiseFloorCallback(onGetNoiseFloor); + modem->setGetStatsCallback(onGetStats); + modem->setGetBatteryCallback(onGetBattery); + modem->setGetSensorsCallback(onGetSensors); modem->begin(); } From 240b5ea1e33fdc44808c87a268e4295cfa474ded Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 31 Jan 2026 15:08:25 +0100 Subject: [PATCH 313/409] Refactor KissModem to integrate radio and sensor management directly, removing callback dependencies. --- examples/kiss_modem/KissModem.cpp | 49 +++++++------------------------ examples/kiss_modem/KissModem.h | 25 +++++----------- examples/kiss_modem/main.cpp | 34 +-------------------- 3 files changed, 20 insertions(+), 88 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index c6e2f2bdcf..d11e8217db 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -1,7 +1,9 @@ #include "KissModem.h" +#include -KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng) - : _serial(serial), _identity(identity), _rng(rng) { +KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, + mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors) + : _serial(serial), _identity(identity), _rng(rng), _radio(radio), _board(board), _sensors(sensors) { _rx_len = 0; _rx_escaped = false; _rx_active = false; @@ -11,12 +13,7 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _setTxPowerCallback = nullptr; _setSyncWordCallback = nullptr; _getCurrentRssiCallback = nullptr; - _isChannelBusyCallback = nullptr; - _getAirtimeCallback = nullptr; - _getNoiseFloorCallback = nullptr; _getStatsCallback = nullptr; - _getBatteryCallback = nullptr; - _getSensorsCallback = nullptr; _config = {0, 0, 0, 0, 0, 0x12}; } @@ -406,12 +403,7 @@ void KissModem::handleGetCurrentRssi() { } void KissModem::handleIsChannelBusy() { - if (!_isChannelBusyCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } - - uint8_t busy = _isChannelBusyCallback() ? 0x01 : 0x00; + uint8_t busy = _radio.isReceiving() ? 0x01 : 0x00; writeFrame(RESP_CHANNEL_BUSY, &busy, 1); } @@ -420,23 +412,14 @@ void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { writeErrorFrame(ERR_INVALID_LENGTH); return; } - if (!_getAirtimeCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } uint8_t packet_len = data[0]; - uint32_t airtime = _getAirtimeCallback(packet_len); + uint32_t airtime = _radio.getEstAirtimeFor(packet_len); writeFrame(RESP_AIRTIME, (uint8_t*)&airtime, 4); } void KissModem::handleGetNoiseFloor() { - if (!_getNoiseFloorCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } - - int16_t noise_floor = _getNoiseFloorCallback(); + int16_t noise_floor = _radio.getNoiseFloor(); writeFrame(RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); } @@ -456,12 +439,7 @@ void KissModem::handleGetStats() { } void KissModem::handleGetBattery() { - if (!_getBatteryCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } - - uint16_t mv = _getBatteryCallback(); + uint16_t mv = _board.getBattMilliVolts(); writeFrame(RESP_BATTERY, (uint8_t*)&mv, 2); } @@ -474,16 +452,11 @@ void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { writeErrorFrame(ERR_INVALID_LENGTH); return; } - if (!_getSensorsCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } uint8_t permissions = data[0]; - uint8_t buf[255]; - uint8_t result_len = _getSensorsCallback(permissions, buf, 255); - if (result_len > 0) { - writeFrame(RESP_SENSORS, buf, result_len); + CayenneLPP telemetry(255); + if (_sensors.querySensors(permissions, telemetry)) { + writeFrame(RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); } else { writeFrame(RESP_SENSORS, nullptr, 0); } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index e223d92d1a..bc7560f487 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include #define KISS_FEND 0xC0 #define KISS_FESC 0xDB @@ -69,18 +71,13 @@ #define ERR_ENCRYPT_FAILED 0x06 #define ERR_TX_PENDING 0x07 -#define KISS_FIRMWARE_VERSION 2 +#define KISS_FIRMWARE_VERSION 1 typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); typedef void (*SetSyncWordCallback)(uint8_t syncWord); typedef float (*GetCurrentRssiCallback)(); -typedef bool (*IsChannelBusyCallback)(); -typedef uint32_t (*GetAirtimeCallback)(uint8_t len); -typedef int16_t (*GetNoiseFloorCallback)(); typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); -typedef uint16_t (*GetBatteryCallback)(); -typedef uint8_t (*GetSensorsCallback)(uint8_t permissions, uint8_t* buffer, uint8_t max_len); struct RadioConfig { uint32_t freq_hz; @@ -95,6 +92,9 @@ class KissModem { Stream& _serial; mesh::LocalIdentity& _identity; mesh::RNG& _rng; + mesh::Radio& _radio; + mesh::MainBoard& _board; + SensorManager& _sensors; uint8_t _rx_buf[KISS_MAX_FRAME_SIZE]; uint16_t _rx_len; @@ -109,12 +109,7 @@ class KissModem { SetTxPowerCallback _setTxPowerCallback; SetSyncWordCallback _setSyncWordCallback; GetCurrentRssiCallback _getCurrentRssiCallback; - IsChannelBusyCallback _isChannelBusyCallback; - GetAirtimeCallback _getAirtimeCallback; - GetNoiseFloorCallback _getNoiseFloorCallback; GetStatsCallback _getStatsCallback; - GetBatteryCallback _getBatteryCallback; - GetSensorsCallback _getSensorsCallback; RadioConfig _config; @@ -148,7 +143,8 @@ class KissModem { void handleGetSensors(const uint8_t* data, uint16_t len); public: - KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng); + KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, + mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors); void begin(); void loop(); @@ -157,12 +153,7 @@ class KissModem { void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } - void setIsChannelBusyCallback(IsChannelBusyCallback cb) { _isChannelBusyCallback = cb; } - void setGetAirtimeCallback(GetAirtimeCallback cb) { _getAirtimeCallback = cb; } - void setGetNoiseFloorCallback(GetNoiseFloorCallback cb) { _getNoiseFloorCallback = cb; } void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } - void setGetBatteryCallback(GetBatteryCallback cb) { _getBatteryCallback = cb; } - void setGetSensorsCallback(GetSensorsCallback cb) { _getSensorsCallback = cb; } bool getPacketToSend(uint8_t* packet, uint16_t* len); void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 0a54c9d356..e81161bf38 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include "KissModem.h" #if defined(NRF52_PLATFORM) @@ -61,38 +60,12 @@ float onGetCurrentRssi() { return radio_driver.getCurrentRSSI(); } -bool onIsChannelBusy() { - return radio_driver.isReceiving(); -} - -uint32_t onGetAirtime(uint8_t len) { - return radio_driver.getEstAirtimeFor(len); -} - -int16_t onGetNoiseFloor() { - return radio_driver.getNoiseFloor(); -} - void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { *rx = radio_driver.getPacketsRecv(); *tx = radio_driver.getPacketsSent(); *errors = radio_driver.getPacketsRecvErrors(); } -uint16_t onGetBattery() { - return board.getBattMilliVolts(); -} - -uint8_t onGetSensors(uint8_t permissions, uint8_t* buffer, uint8_t max_len) { - CayenneLPP telemetry(max_len); - if (sensors.querySensors(permissions, telemetry)) { - uint8_t len = telemetry.getSize(); - memcpy(buffer, telemetry.getBuffer(), len); - return len; - } - return 0; -} - void setup() { board.begin(); @@ -112,17 +85,12 @@ void setup() { sensors.begin(); - modem = new KissModem(Serial, identity, rng); + modem = new KissModem(Serial, identity, rng, radio_driver, board, sensors); modem->setRadioCallback(onSetRadio); modem->setTxPowerCallback(onSetTxPower); modem->setSyncWordCallback(onSetSyncWord); modem->setGetCurrentRssiCallback(onGetCurrentRssi); - modem->setIsChannelBusyCallback(onIsChannelBusy); - modem->setGetAirtimeCallback(onGetAirtime); - modem->setGetNoiseFloorCallback(onGetNoiseFloor); modem->setGetStatsCallback(onGetStats); - modem->setGetBatteryCallback(onGetBattery); - modem->setGetSensorsCallback(onGetSensors); modem->begin(); } From 2b754d4295330f1699e7152384def49a8c8e4408 Mon Sep 17 00:00:00 2001 From: Matthias Wientapper Date: Sat, 31 Jan 2026 23:17:48 +0100 Subject: [PATCH 314/409] cli_commands.md: `region` available via remote cli in 1.12.0 changed with #1476 --- docs/cli_commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 6b4f61578b..c316bd6c77 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -642,7 +642,7 @@ **Usage:** - `region` -**Serial Only:** Yes +**Serial Only:** For firmware older than 1.12.0 --- From a342ab8437e19ea792e51e46cd14c333c1c8b609 Mon Sep 17 00:00:00 2001 From: taco Date: Sun, 1 Feb 2026 14:46:55 +1100 Subject: [PATCH 315/409] nrf52: allow repeater to sleep when idle --- examples/simple_repeater/main.cpp | 9 ++++++--- src/helpers/NRF52Board.cpp | 26 ++++++++++++++++++++++++++ src/helpers/NRF52Board.h | 1 + 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d55d611865..eb4b5b0988 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -127,14 +127,17 @@ void 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()) { // No pending work. Safe to sleep + if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { + #if defined(NRF52_PLATFORM) + board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible + #else + if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep 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 } + #endif } } diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 6915c85635..1db858f508 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -251,6 +251,32 @@ void NRF52BoardDCDC::begin() { } } +void NRF52Board::sleep(uint32_t secs) { + // Clear FPU interrupt flags to avoid insomnia + // see errata 87 for details https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html + #if (__FPU_USED == 1) + __set_FPSCR(__get_FPSCR() & ~(0x0000009F)); + (void) __get_FPSCR(); + NVIC_ClearPendingIRQ(FPU_IRQn); + #endif + + // On nRF52, we use event-driven sleep instead of timed sleep + // The 'secs' parameter is ignored - we wake on any interrupt + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + + if (sd_enabled) { + // first call processes pending softdevice events, second call sleeps. + sd_app_evt_wait(); + sd_app_evt_wait(); + } else { + // softdevice is disabled, use raw WFE + __SEV(); + __WFE(); + __WFE(); + } +} + // 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 1c70d8f05c..0332af0781 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -51,6 +51,7 @@ class NRF52Board : public mesh::MainBoard { virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } virtual bool startOTAUpdate(const char *id, char reply[]) override; + virtual void sleep(uint32_t secs) override; #ifdef NRF52_POWER_MANAGEMENT bool isExternalPowered() override; From 223930765cbb0dabcd171444f54db6f6d6771cf1 Mon Sep 17 00:00:00 2001 From: Jan Pinkas Date: Sun, 1 Feb 2026 09:00:01 +0100 Subject: [PATCH 316/409] Enable I2C sensors and EnvironmentSensorManager for Heltec T114 --- variants/heltec_t114/platformio.ini | 5 +++ variants/heltec_t114/target.cpp | 67 +++-------------------------- variants/heltec_t114/target.h | 18 ++------ 3 files changed, 14 insertions(+), 76 deletions(-) diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 20f5e8fec9..dd1f8bb3cb 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 @@ -35,11 +36,15 @@ build_flags = ${nrf52_base.build_flags} -D PIN_GPS_EN=21 -D PIN_GPS_RESET=38 -D PIN_GPS_RESET_ACTIVE=LOW + -D PIN_BOARD_SDA=16 + -D PIN_BOARD_SCL=13 build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/heltec_t114> lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit GFX Library @ ^1.12.1 debug_tool = jlink diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index c3341103a5..23b9b667bc 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -45,26 +45,12 @@ mesh::LocalIdentity radio_new_identity() { 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 + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS // Give GPS a moment to power up and send data delay(1500); @@ -77,57 +63,16 @@ bool T114SensorManager::begin() { } else { MESH_DEBUG_PRINTLN("No GPS detected"); } - digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed + digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed - return true; + return EnvironmentSensorManager::begin(); } bool T114SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + EnvironmentSensorManager::querySensors(requester_permissions, 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 -} diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 6306cd699c..24de81eeed 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #ifdef DISPLAY_CLASS @@ -18,23 +18,11 @@ #endif #endif -class T114SensorManager : public SensorManager { - bool gps_active = false; - bool gps_detected = false; - LocationProvider* _location; - - void start_gps(); - void stop_gps(); +class T114SensorManager : public EnvironmentSensorManager { public: - T114SensorManager(LocationProvider &location): _location(&location) { } + T114SensorManager(LocationProvider &location): EnvironmentSensorManager(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; }; extern T114Board board; From e15503d50d07fbceca16234078596669f0477820 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Mon, 2 Feb 2026 14:19:42 +0800 Subject: [PATCH 317/409] Fix low power consumption issues --- src/helpers/ui/SSD1306Display.cpp | 12 +++++++++--- variants/heltec_v4/platformio.ini | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 4e7fd10ad0..f585feb07d 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -18,17 +18,23 @@ bool SSD1306Display::begin() { } void SSD1306Display::turnOn() { - display.ssd1306_command(SSD1306_DISPLAYON); if (!_isOn) { - if (_peripher_power) _peripher_power->claim(); + if (_peripher_power) { + _peripher_power->claim(); + begin(); + } _isOn = true; } + display.ssd1306_command(SSD1306_DISPLAYON); } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); if (_isOn) { - if (_peripher_power) _peripher_power->release(); + if (_peripher_power) { + if (PIN_OLED_RESET >= 0) digitalWrite(PIN_OLED_RESET, LOW); + _peripher_power->release(); + } _isOn = false; } } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ba75900940..fe971f065a 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -52,6 +52,7 @@ build_flags = -D HELTEC_LORA_V4_OLED -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 + -D PIN_OLED_RESET=21 -D ENV_PIN_SDA=4 -D ENV_PIN_SCL=3 build_src_filter= ${Heltec_lora32_v4.build_src_filter} From f0ba14ff7580fd8dfc866ec0490606485390cb3c Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Mon, 2 Feb 2026 18:05:26 +0100 Subject: [PATCH 318/409] Remove sync word handling from KissModem. --- examples/kiss_modem/KissModem.cpp | 28 +--------------------------- examples/kiss_modem/KissModem.h | 9 --------- examples/kiss_modem/main.cpp | 5 ----- 3 files changed, 1 insertion(+), 41 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index d11e8217db..d9c71bf859 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -11,10 +11,9 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _pending_tx_len = 0; _setRadioCallback = nullptr; _setTxPowerCallback = nullptr; - _setSyncWordCallback = nullptr; _getCurrentRssiCallback = nullptr; _getStatsCallback = nullptr; - _config = {0, 0, 0, 0, 0, 0x12}; + _config = {0, 0, 0, 0, 0}; } void KissModem::begin() { @@ -133,18 +132,12 @@ void KissModem::processFrame() { case CMD_SET_TX_POWER: handleSetTxPower(data, data_len); break; - case CMD_SET_SYNC_WORD: - handleSetSyncWord(data, data_len); - break; case CMD_GET_RADIO: handleGetRadio(); break; case CMD_GET_TX_POWER: handleGetTxPower(); break; - case CMD_GET_SYNC_WORD: - handleGetSyncWord(); - break; case CMD_GET_VERSION: handleGetVersion(); break; @@ -347,21 +340,6 @@ void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) { writeFrame(RESP_OK, nullptr, 0); } -void KissModem::handleSetSyncWord(const uint8_t* data, uint16_t len) { - if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); - return; - } - if (!_setSyncWordCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } - - _config.sync_word = data[0]; - _setSyncWordCallback(data[0]); - writeFrame(RESP_OK, nullptr, 0); -} - void KissModem::handleGetRadio() { uint8_t buf[10]; memcpy(buf, &_config.freq_hz, 4); @@ -375,10 +353,6 @@ void KissModem::handleGetTxPower() { writeFrame(RESP_TX_POWER, &_config.tx_power, 1); } -void KissModem::handleGetSyncWord() { - writeFrame(RESP_SYNC_WORD, &_config.sync_word, 1); -} - void KissModem::handleGetVersion() { uint8_t buf[2]; buf[0] = KISS_FIRMWARE_VERSION; diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index bc7560f487..170bb0c2a4 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -25,10 +25,8 @@ #define CMD_HASH 0x08 #define CMD_SET_RADIO 0x09 #define CMD_SET_TX_POWER 0x0A -#define CMD_SET_SYNC_WORD 0x0B #define CMD_GET_RADIO 0x0C #define CMD_GET_TX_POWER 0x0D -#define CMD_GET_SYNC_WORD 0x0E #define CMD_GET_VERSION 0x0F #define CMD_GET_CURRENT_RSSI 0x10 #define CMD_IS_CHANNEL_BUSY 0x11 @@ -50,7 +48,6 @@ #define RESP_OK 0x29 #define RESP_RADIO 0x2A #define RESP_TX_POWER 0x2B -#define RESP_SYNC_WORD 0x2C #define RESP_VERSION 0x2D #define RESP_ERROR 0x2E #define RESP_TX_DONE 0x2F @@ -75,7 +72,6 @@ typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); -typedef void (*SetSyncWordCallback)(uint8_t syncWord); typedef float (*GetCurrentRssiCallback)(); typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); @@ -85,7 +81,6 @@ struct RadioConfig { uint8_t sf; uint8_t cr; uint8_t tx_power; - uint8_t sync_word; }; class KissModem { @@ -107,7 +102,6 @@ class KissModem { SetRadioCallback _setRadioCallback; SetTxPowerCallback _setTxPowerCallback; - SetSyncWordCallback _setSyncWordCallback; GetCurrentRssiCallback _getCurrentRssiCallback; GetStatsCallback _getStatsCallback; @@ -128,10 +122,8 @@ class KissModem { void handleHash(const uint8_t* data, uint16_t len); void handleSetRadio(const uint8_t* data, uint16_t len); void handleSetTxPower(const uint8_t* data, uint16_t len); - void handleSetSyncWord(const uint8_t* data, uint16_t len); void handleGetRadio(); void handleGetTxPower(); - void handleGetSyncWord(); void handleGetVersion(); void handleGetCurrentRssi(); void handleIsChannelBusy(); @@ -151,7 +143,6 @@ class KissModem { void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } - void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index e81161bf38..959222b9f8 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -52,10 +52,6 @@ void onSetTxPower(uint8_t power) { radio_set_tx_power(power); } -void onSetSyncWord(uint8_t sync_word) { - radio_set_sync_word(sync_word); -} - float onGetCurrentRssi() { return radio_driver.getCurrentRSSI(); } @@ -88,7 +84,6 @@ void setup() { modem = new KissModem(Serial, identity, rng, radio_driver, board, sensors); modem->setRadioCallback(onSetRadio); modem->setTxPowerCallback(onSetTxPower); - modem->setSyncWordCallback(onSetSyncWord); modem->setGetCurrentRssiCallback(onGetCurrentRssi); modem->setGetStatsCallback(onGetStats); modem->begin(); From 84e68cf4cb2dc06f83c861863621e0749ff18700 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Mon, 2 Feb 2026 22:58:55 +0100 Subject: [PATCH 319/409] initial port of M5Stack Unit C6L, update pioarduino to newer bugfix release --- platformio.ini | 6 +- variants/m5stack_unit_c6l/UnitC6LBoard.cpp | 49 ++++++++++ variants/m5stack_unit_c6l/UnitC6LBoard.h | 15 +++ variants/m5stack_unit_c6l/platformio.ini | 104 +++++++++++++++++++++ variants/m5stack_unit_c6l/target.h | 21 +++++ 5 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 variants/m5stack_unit_c6l/UnitC6LBoard.cpp create mode 100644 variants/m5stack_unit_c6l/UnitC6LBoard.h create mode 100644 variants/m5stack_unit_c6l/platformio.ini create mode 100644 variants/m5stack_unit_c6l/target.h diff --git a/platformio.ini b/platformio.ini index 743e357afe..69883271d3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -68,10 +68,10 @@ lib_deps = file://arch/esp32/AsyncElegantOTA ; esp32c6 uses arduino framework 3.x -; WARNING: experimental. pioarduino on esp32c6 needs work - it's not considered stable and has issues. +; WARNING: experimental. May not work as stable as other platforms. [esp32c6_base] extends = esp32_base -platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13-1/platform-espressif32.zip ; ----------------- NRF52 --------------------- @@ -80,7 +80,7 @@ extends = arduino_base platform = nordicnrf52 platform_packages = framework-arduinoadafruitnrf52 @ 1.10700.0 -extra_scripts = +extra_scripts = create-uf2.py arch/nrf52/extra_scripts/patch_bluefruit.py build_flags = ${arduino_base.build_flags} diff --git a/variants/m5stack_unit_c6l/UnitC6LBoard.cpp b/variants/m5stack_unit_c6l/UnitC6LBoard.cpp new file mode 100644 index 0000000000..6538ef4840 --- /dev/null +++ b/variants/m5stack_unit_c6l/UnitC6LBoard.cpp @@ -0,0 +1,49 @@ +#include +#include "target.h" + +UnitC6LBoard board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(0); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/m5stack_unit_c6l/UnitC6LBoard.h b/variants/m5stack_unit_c6l/UnitC6LBoard.h new file mode 100644 index 0000000000..a4ea3ee6e5 --- /dev/null +++ b/variants/m5stack_unit_c6l/UnitC6LBoard.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +class UnitC6LBoard : public ESP32Board { +public: + void begin() { + ESP32Board::begin(); + } + + const char* getManufacturerName() const override { + return "Unit C6L"; + } +}; diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini new file mode 100644 index 0000000000..bbfdb4a119 --- /dev/null +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -0,0 +1,104 @@ +[M5Stack_Unit_C6L] +extends = esp32c6_base +board = esp32-c6-devkitm-1 +board_build.partitions = min_spiffs.csv ; get around 4mb flash limit +build_flags = + ${esp32c6_base.build_flags} + ${sensor_base.build_flags} + -I variants/M5Stack_Unit_C6L + -D P_LORA_TX_LED=15 + -D P_LORA_SCLK=20 + -D P_LORA_MISO=22 + -D P_LORA_MOSI=21 + -D P_LORA_NSS=23 + -D P_LORA_DIO_1=7 + -D P_LORA_BUSY=19 + -D P_LORA_RESET=-1 + -D PIN_BUZZER=11 + -D PIN_BOARD_SDA=16 + -D PIN_BOARD_SCL=17 + -D SX126X_RXEN=5 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D DISABLE_WIFI_OTA=1 + -D GPS_RX=4 + -D GPS_TX=5 +build_src_filter = ${esp32c6_base.build_src_filter} + +<../variants/m5stack_unit_c6l> + + +lib_deps = + ${esp32c6_base.lib_deps} + ${sensor_base.lib_deps} + +[env:M5Stack_Unit_C6L_repeater] +extends = M5Stack_Unit_C6L +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${M5Stack_Unit_C6L.build_flags} + -D ADVERT_NAME='"Unit C6L 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 +lib_deps = + ${M5Stack_Unit_C6L.lib_deps} +; ${esp32_ota.lib_deps} + +[env:M5Stack_Unit_C6L_room_server] +extends = M5Stack_Unit_C6L +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${M5Stack_Unit_C6L.build_flags} + -D ADVERT_NAME='"Unit C6L 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 +lib_deps = + ${M5Stack_Unit_C6L.lib_deps} +; ${esp32_ota.lib_deps} + +[env:M5Stack_Unit_C6L_companion_radio_ble] +extends = M5Stack_Unit_C6L +build_flags = ${M5Stack_Unit_C6L.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + + + - + +<../examples/companion_radio/*.cpp> +lib_deps = + ${M5Stack_Unit_C6L.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:M5Stack_Unit_C6L_companion_radio_usb] +extends = M5Stack_Unit_C6L +build_flags = ${M5Stack_Unit_C6L.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + + + - + +<../examples/companion_radio/*.cpp> +lib_deps = + ${M5Stack_Unit_C6L.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/variants/m5stack_unit_c6l/target.h b/variants/m5stack_unit_c6l/target.h new file mode 100644 index 0000000000..1f4e9ae32d --- /dev/null +++ b/variants/m5stack_unit_c6l/target.h @@ -0,0 +1,21 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +extern UnitC6LBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager 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(); From 598489be471e978ea76d6f759f6367124a0b335a Mon Sep 17 00:00:00 2001 From: taco Date: Mon, 26 Jan 2026 16:41:08 +1100 Subject: [PATCH 320/409] refactor ui with ring buffer and display most recent --- examples/companion_radio/ui-new/UITask.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 0690b45ac6..ae2d93753f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -458,15 +458,17 @@ class MsgPreviewScreen : public UIScreen { }; #define MAX_UNREAD_MSGS 32 int num_unread; + int head = MAX_UNREAD_MSGS - 1; // index of latest unread message MsgEntry unread[MAX_UNREAD_MSGS]; public: MsgPreviewScreen(UITask* task, mesh::RTCClock* rtc) : _task(task), _rtc(rtc) { num_unread = 0; } void addPreview(uint8_t path_len, const char* from_name, const char* msg) { - if (num_unread >= MAX_UNREAD_MSGS) return; // full + head = (head + 1) % MAX_UNREAD_MSGS; + if (num_unread < MAX_UNREAD_MSGS) num_unread++; - auto p = &unread[num_unread++]; + auto p = &unread[head]; p->timestamp = _rtc->getCurrentTime(); if (path_len == 0xFF) { sprintf(p->origin, "(D) %s:", from_name); @@ -484,7 +486,7 @@ class MsgPreviewScreen : public UIScreen { sprintf(tmp, "Unread: %d", num_unread); display.print(tmp); - auto p = &unread[0]; + auto p = &unread[head]; int secs = _rtc->getCurrentTime() - p->timestamp; if (secs < 60) { @@ -520,14 +522,10 @@ class MsgPreviewScreen : public UIScreen { bool handleInput(char c) override { if (c == KEY_NEXT || c == KEY_RIGHT) { + head = (head + MAX_UNREAD_MSGS - 1) % MAX_UNREAD_MSGS; num_unread--; if (num_unread == 0) { _task->gotoHomeScreen(); - } else { - // delete first/curr item from unread queue - for (int i = 0; i < num_unread; i++) { - unread[i] = unread[i + 1]; - } } return true; } From 0fb570338f57b6b2fdb5426f92e6e61c58e9db38 Mon Sep 17 00:00:00 2001 From: agessaman Date: Tue, 3 Feb 2026 20:58:37 -0800 Subject: [PATCH 321/409] fix(kiss): periodic noise floor calibration and AGC reset - Trigger noise floor calibration every 2s and AGC reset every 30s in main loop. - Reorder loop to match Dispatcher: calibrate + radio.loop() before AGC reset and recvRaw() so RSSI is never sampled right after startReceive(). - Update protocol doc with calibration intervals and typical noise floor range. - Variant platformio.ini updates (heltec_v3, rak4631). --- docs/kiss_modem_protocol.md | 20 ++++++++++++++++---- examples/kiss_modem/main.cpp | 23 +++++++++++++++++++---- variants/heltec_v3/platformio.ini | 9 +++++++++ variants/rak4631/platformio.ini | 11 ++++++++++- 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index e80c3b29ba..067e153974 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -28,6 +28,8 @@ Maximum unescaped frame size: 512 bytes. ## Commands +Command and response codes below are taken from `examples/kiss_modem/KissModem.h` and the switch in `KissModem::processFrame()`. + ### Request Commands (Host → Modem) | Command | Value | Data | @@ -43,10 +45,10 @@ Maximum unescaped frame size: 512 bytes. | `CMD_HASH` | `0x08` | Data to hash | | `CMD_SET_RADIO` | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | | `CMD_SET_TX_POWER` | `0x0A` | Power dBm (1) | -| `CMD_SET_SYNC_WORD` | `0x0B` | Sync word (1) | +| *reserved* | `0x0B` | *(not implemented)* | | `CMD_GET_RADIO` | `0x0C` | - | | `CMD_GET_TX_POWER` | `0x0D` | - | -| `CMD_GET_SYNC_WORD` | `0x0E` | - | +| *reserved* | `0x0E` | *(not implemented)* | | `CMD_GET_VERSION` | `0x0F` | - | | `CMD_GET_CURRENT_RSSI` | `0x10` | - | | `CMD_IS_CHANNEL_BUSY` | `0x11` | - | @@ -73,7 +75,7 @@ Maximum unescaped frame size: 512 bytes. | `RESP_OK` | `0x29` | - | | `RESP_RADIO` | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | | `RESP_TX_POWER` | `0x2B` | Power dBm (1) | -| `RESP_SYNC_WORD` | `0x2C` | Sync word (1) | +| *reserved* | `0x2C` | *(not implemented)* | | `RESP_VERSION` | `0x2D` | Version (1) + Reserved (1) | | `RESP_ERROR` | `0x2E` | Error code (1) | | `RESP_TX_DONE` | `0x2F` | Result (1): 0x00=failed, 0x01=success | @@ -119,9 +121,19 @@ All values little-endian. | RSSI | 1 byte | Signal strength dBm, signed | | Packet | variable | Raw MeshCore packet | +### Noise Floor (RESP_NOISE_FLOOR) + +Response to `CMD_GET_NOISE_FLOOR` (0x13). Little-endian. + +| Field | Size | Description | +|--------------|------|--------------------------------| +| Noise floor | 2 | int16_t, dBm (signed), e.g. -120 | + +The modem recalibrates the noise floor periodically (every 2 s) from RX samples when idle. The receiver AGC is also reset periodically (every 30 s) so RSSI and noise floor do not drift to the minimum (-120). Typical range after calibration is about -120 to -90 dBm. Values may be 0 or briefly stale until the radio has been in receive mode long enough to collect 64 samples. + ### Stats (RESP_STATS) -All values little-endian. +Response to `CMD_GET_STATS` (0x14). All values little-endian. | Field | Size | Description | |-------|------|-------------| diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 959222b9f8..13855309bb 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -12,9 +12,14 @@ #include #endif +#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 // match Dispatcher default +#define AGC_RESET_INTERVAL_MS 30000 // periodic RX restart so AGC doesn't drift (repeater uses same via prefs) + StdRNG rng; mesh::LocalIdentity identity; KissModem* modem; +static uint32_t next_noise_floor_calib_ms = 0; +static uint32_t next_agc_reset_ms = 0; void halt() { while (1) ; @@ -94,7 +99,16 @@ void loop() { uint8_t packet[KISS_MAX_PACKET_SIZE]; uint16_t len; - + + // Match Dispatcher order: noise floor calib + loop() first, so we never sample RSSI in the same + // iteration as startReceive() (AGC reset -> recvRaw below). Sampling right after startReceive() + // can yield settling/cold RSSI and drive the floor toward -120. + if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { + radio_driver.triggerNoiseFloorCalibrate(0); // 0 = no interference threshold (KISS has no prefs) + next_noise_floor_calib_ms = millis(); + } + radio_driver.loop(); + if (modem->getPacketToSend(packet, &len)) { radio_driver.startSendRaw(packet, len); while (!radio_driver.isSendComplete()) { @@ -104,14 +118,15 @@ void loop() { modem->onTxComplete(true); } + if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { + radio_driver.resetAGC(); // next recvRaw() will startReceive() and reset AGC so RSSI/noise floor don't stick at -120 + next_agc_reset_ms = millis(); + } uint8_t rx_buf[256]; int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); - if (rx_len > 0) { int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); int8_t rssi = (int8_t)radio_driver.getLastRSSI(); modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } - - radio_driver.loop(); } diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 6b61eff5d5..4d299104e4 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -367,3 +367,12 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} + +[env:Heltec_v3_kiss_modem] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/kiss_modem/> +lib_deps = + ${Heltec_lora32_v3.lib_deps} \ No newline at end of file diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 9a9ab2dd30..737ef5652f 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -183,4 +183,13 @@ build_flags = -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} + - +<../examples/simple_sensor> \ No newline at end of file + +<../examples/simple_sensor> + +[env:RAK_4631_kiss_modem] +extends = rak4631 +build_flags = + ${rak4631.build_flags} +build_src_filter = ${rak4631.build_src_filter} + +<../examples/kiss_modem/> +lib_deps = + ${rak4631.lib_deps} \ No newline at end of file From 5cb26b91f6b35ebb452887fa458513790052ff57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Thu, 5 Feb 2026 13:35:04 +0000 Subject: [PATCH 322/409] Refactor Heltec T114 sensor management --- examples/simple_repeater/main.cpp | 7 ++++ variants/heltec_t114/platformio.ini | 12 +++--- variants/heltec_t114/target.cpp | 62 +++++++++++------------------ variants/heltec_t114/target.h | 17 +++----- variants/heltec_t114/variant.h | 9 +++-- 5 files changed, 48 insertions(+), 59 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d55d611865..c053b68bd9 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -29,6 +29,12 @@ void setup() { board.begin(); +#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM) + // give some extra time for serial to settle so + // boot debug messages can be seen on terminal + delay(5000); +#endif + // For power saving lastActive = millis(); // mark last active time since boot @@ -42,6 +48,7 @@ void setup() { #endif if (!radio_init()) { + MESH_DEBUG_PRINTLN("Radio init failed!"); halt(); } diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index dd1f8bb3cb..b985030f79 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -29,15 +29,13 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D DISPLAY_CLASS=NullDisplayDriver - -D ST7789 -D PIN_GPS_RX=39 -D PIN_GPS_TX=37 -D PIN_GPS_EN=21 -D PIN_GPS_RESET=38 -D PIN_GPS_RESET_ACTIVE=LOW - -D PIN_BOARD_SDA=16 - -D PIN_BOARD_SCL=13 + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL build_src_filter = ${nrf52_base.build_src_filter} + + @@ -45,8 +43,6 @@ build_src_filter = ${nrf52_base.build_src_filter} lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} - stevemarple/MicroNMEA @ ^2.0.6 - adafruit/Adafruit GFX Library @ ^1.12.1 debug_tool = jlink upload_protocol = nrfutil @@ -105,6 +101,7 @@ board_upload.maximum_size = 712704 build_flags = ${Heltec_t114.build_flags} -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 @@ -127,6 +124,7 @@ board_upload.maximum_size = 712704 build_flags = ${Heltec_t114.build_flags} -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 ; -D BLE_PIN_CODE=123456 @@ -149,6 +147,7 @@ extends = Heltec_t114 board = heltec_t114 board_build.ldscript = boards/nrf52840_s140_v6.ld build_flags = ${Heltec_t114.build_flags} + -D ST7789 -D HELTEC_T114_WITH_DISPLAY -D DISPLAY_CLASS=ST7789Display build_src_filter = ${Heltec_t114.build_src_filter} @@ -158,6 +157,7 @@ build_src_filter = ${Heltec_t114.build_src_filter} + lib_deps = ${Heltec_t114.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 debug_tool = jlink upload_protocol = nrfutil diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index 23b9b667bc..cd280ddab7 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -1,28 +1,46 @@ -#include #include "target.h" + +#include #include + +#ifdef ENV_INCLUDE_GPS #include +#endif T114Board board; +#if defined(P_LORA_SCLK) RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); +#else +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif WRAPPER_CLASS radio_driver(radio, board); 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); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors; +#endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display; - MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { rtc_clock.begin(Wire); +#if defined(P_LORA_SCLK) return radio.std_init(&SPI); +#else + return radio.std_init(); +#endif } uint32_t radio_get_rng_seed() { @@ -42,37 +60,5 @@ void radio_set_tx_power(uint8_t dbm) { mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); - return mesh::LocalIdentity(&rng); // create new random identity -} - -bool T114SensorManager::begin() { - Serial1.begin(9600); - - // Try to detect if GPS is physically connected to determine if we should expose the setting - pinMode(PIN_GPS_EN, OUTPUT); - digitalWrite(PIN_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(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed - - return EnvironmentSensorManager::begin(); -} - -bool T114SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { - EnvironmentSensorManager::querySensors(requester_permissions, telemetry); - - if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); - } - return true; + return mesh::LocalIdentity(&rng); // create new random identity } diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 24de81eeed..94f990ecc4 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -2,10 +2,10 @@ #define RADIOLIB_STATIC_ONLY 1 #include -#include #include -#include #include +#include +#include #include #include @@ -18,21 +18,14 @@ #endif #endif -class T114SensorManager : public EnvironmentSensorManager { -public: - T114SensorManager(LocationProvider &location): EnvironmentSensorManager(location) { } - bool begin() override; - bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; -}; - 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; - extern MomentaryButton user_btn; +extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index aa7f40222b..bfb4484d13 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -14,7 +14,7 @@ #define USE_LFXO // 32.768 kHz crystal oscillator #define VARIANT_MCK (64000000ul) -#define WIRE_INTERFACES_COUNT (1) +#define WIRE_INTERFACES_COUNT (2) //////////////////////////////////////////////////////////////////////////////// // Power @@ -58,8 +58,11 @@ //////////////////////////////////////////////////////////////////////////////// // I2C pin definition -#define PIN_WIRE_SDA (26) // P0.26 -#define PIN_WIRE_SCL (27) // P0.27 +#define PIN_WIRE_SDA (26) // P0.26 +#define PIN_WIRE_SCL (27) // P0.27 + +#define PIN_WIRE1_SDA (7) // P0.8 +#define PIN_WIRE1_SCL (8) // P0.7 //////////////////////////////////////////////////////////////////////////////// // SPI pin definition From c0b81b9ad867dd0b018c4a513b0424995226fb97 Mon Sep 17 00:00:00 2001 From: Adam Gessaman Date: Thu, 5 Feb 2026 09:46:30 -0800 Subject: [PATCH 323/409] Clean up comments on kiss noise floor changes. --- docs/kiss_modem_protocol.md | 4 +--- examples/kiss_modem/main.cpp | 12 +++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 067e153974..00b0bf90f8 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -28,8 +28,6 @@ Maximum unescaped frame size: 512 bytes. ## Commands -Command and response codes below are taken from `examples/kiss_modem/KissModem.h` and the switch in `KissModem::processFrame()`. - ### Request Commands (Host → Modem) | Command | Value | Data | @@ -129,7 +127,7 @@ Response to `CMD_GET_NOISE_FLOOR` (0x13). Little-endian. |--------------|------|--------------------------------| | Noise floor | 2 | int16_t, dBm (signed), e.g. -120 | -The modem recalibrates the noise floor periodically (every 2 s) from RX samples when idle. The receiver AGC is also reset periodically (every 30 s) so RSSI and noise floor do not drift to the minimum (-120). Typical range after calibration is about -120 to -90 dBm. Values may be 0 or briefly stale until the radio has been in receive mode long enough to collect 64 samples. +The modem recalibrates the noise floor every two seconds with an AGC reset every 30 seconds. ### Stats (RESP_STATS) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 13855309bb..3a610460d8 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -12,8 +12,8 @@ #include #endif -#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 // match Dispatcher default -#define AGC_RESET_INTERVAL_MS 30000 // periodic RX restart so AGC doesn't drift (repeater uses same via prefs) +#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 +#define AGC_RESET_INTERVAL_MS 30000 StdRNG rng; mesh::LocalIdentity identity; @@ -100,11 +100,9 @@ void loop() { uint8_t packet[KISS_MAX_PACKET_SIZE]; uint16_t len; - // Match Dispatcher order: noise floor calib + loop() first, so we never sample RSSI in the same - // iteration as startReceive() (AGC reset -> recvRaw below). Sampling right after startReceive() - // can yield settling/cold RSSI and drive the floor toward -120. + // trigger noise floor calibration if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { - radio_driver.triggerNoiseFloorCalibrate(0); // 0 = no interference threshold (KISS has no prefs) + radio_driver.triggerNoiseFloorCalibrate(0); next_noise_floor_calib_ms = millis(); } radio_driver.loop(); @@ -119,7 +117,7 @@ void loop() { } if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { - radio_driver.resetAGC(); // next recvRaw() will startReceive() and reset AGC so RSSI/noise floor don't stick at -120 + radio_driver.resetAGC(); next_agc_reset_ms = millis(); } uint8_t rx_buf[256]; From d0720c63c2cfa687f7d3a525f4c7a8c5152e44d6 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 3 Jan 2026 20:35:14 +0100 Subject: [PATCH 324/409] Allow negative tx power Like SX1262 allows -9 dBm lowest, some allow lower but that probably isn't useful --- examples/companion_radio/MyMesh.cpp | 2 +- examples/companion_radio/NodePrefs.h | 2 +- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_secure_chat/main.cpp | 4 ++-- examples/simple_sensor/SensorMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/CommonCLI.cpp | 4 ++-- src/helpers/CommonCLI.h | 4 ++-- variants/ebyte_eora_s3/target.cpp | 2 +- variants/ebyte_eora_s3/target.h | 2 +- variants/generic-e22/target.cpp | 2 +- variants/generic-e22/target.h | 2 +- variants/generic_espnow/target.cpp | 2 +- variants/generic_espnow/target.h | 2 +- variants/heltec_ct62/target.cpp | 2 +- variants/heltec_ct62/target.h | 2 +- variants/heltec_e213/target.cpp | 2 +- variants/heltec_e213/target.h | 2 +- variants/heltec_e290/target.cpp | 2 +- variants/heltec_e290/target.h | 2 +- variants/heltec_mesh_solar/target.cpp | 2 +- variants/heltec_mesh_solar/target.h | 2 +- variants/heltec_t114/target.cpp | 2 +- variants/heltec_t114/target.h | 2 +- variants/heltec_t190/target.cpp | 2 +- variants/heltec_t190/target.h | 2 +- variants/heltec_tracker/target.cpp | 2 +- variants/heltec_tracker/target.h | 2 +- variants/heltec_tracker_v2/target.cpp | 2 +- variants/heltec_tracker_v2/target.h | 2 +- variants/heltec_v2/target.cpp | 2 +- variants/heltec_v2/target.h | 2 +- variants/heltec_v3/target.cpp | 2 +- variants/heltec_v3/target.h | 2 +- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 2 +- variants/heltec_wireless_paper/target.cpp | 2 +- variants/heltec_wireless_paper/target.h | 2 +- variants/ikoka_handheld_nrf/target.cpp | 2 +- variants/ikoka_handheld_nrf/target.h | 2 +- variants/ikoka_nano_nrf/target.cpp | 2 +- variants/ikoka_nano_nrf/target.h | 2 +- variants/ikoka_stick_nrf/target.cpp | 2 +- variants/ikoka_stick_nrf/target.h | 2 +- variants/keepteen_lt1/target.cpp | 2 +- variants/keepteen_lt1/target.h | 2 +- variants/lilygo_t3s3/target.cpp | 2 +- variants/lilygo_t3s3/target.h | 2 +- variants/lilygo_t3s3_sx1276/target.cpp | 2 +- variants/lilygo_t3s3_sx1276/target.h | 2 +- variants/lilygo_tbeam_1w/target.cpp | 2 +- variants/lilygo_tbeam_1w/target.h | 2 +- variants/lilygo_tbeam_SX1262/target.cpp | 2 +- variants/lilygo_tbeam_SX1262/target.h | 2 +- variants/lilygo_tbeam_SX1276/target.cpp | 2 +- variants/lilygo_tbeam_SX1276/target.h | 2 +- variants/lilygo_tbeam_supreme_SX1262/target.cpp | 2 +- variants/lilygo_tbeam_supreme_SX1262/target.h | 2 +- variants/lilygo_tdeck/target.cpp | 2 +- variants/lilygo_tdeck/target.h | 2 +- variants/lilygo_techo/target.cpp | 2 +- variants/lilygo_techo/target.h | 2 +- variants/lilygo_techo_lite/target.cpp | 2 +- variants/lilygo_techo_lite/target.h | 2 +- variants/lilygo_tlora_c6/target.cpp | 2 +- variants/lilygo_tlora_c6/target.h | 2 +- variants/lilygo_tlora_v2_1/target.cpp | 2 +- variants/lilygo_tlora_v2_1/target.h | 2 +- variants/mesh_pocket/target.cpp | 2 +- variants/mesh_pocket/target.h | 2 +- variants/meshadventurer/target.cpp | 2 +- variants/meshadventurer/target.h | 2 +- variants/meshtiny/target.cpp | 2 +- variants/meshtiny/target.h | 2 +- variants/minewsemi_me25ls01/target.cpp | 2 +- variants/minewsemi_me25ls01/target.h | 2 +- variants/nano_g2_ultra/target.cpp | 2 +- variants/nano_g2_ultra/target.h | 2 +- variants/nibble_screen_connect/target.cpp | 2 +- variants/nibble_screen_connect/target.h | 2 +- variants/promicro/target.cpp | 2 +- variants/promicro/target.h | 2 +- variants/rak11310/target.cpp | 2 +- variants/rak11310/target.h | 2 +- variants/rak3112/target.cpp | 2 +- variants/rak3112/target.h | 2 +- variants/rak3401/target.cpp | 2 +- variants/rak3401/target.h | 2 +- variants/rak3x72/target.cpp | 2 +- variants/rak3x72/target.h | 2 +- variants/rak4631/target.cpp | 2 +- variants/rak4631/target.h | 2 +- variants/rak_wismesh_tag/target.cpp | 2 +- variants/rak_wismesh_tag/target.h | 2 +- variants/rpi_picow/target.cpp | 2 +- variants/rpi_picow/target.h | 2 +- variants/sensecap_indicator-espnow/target.cpp | 2 +- variants/sensecap_indicator-espnow/target.h | 2 +- variants/sensecap_solar/target.cpp | 2 +- variants/sensecap_solar/target.h | 2 +- variants/station_g2/target.cpp | 2 +- variants/station_g2/target.h | 2 +- variants/t1000-e/target.cpp | 2 +- variants/t1000-e/target.h | 2 +- variants/tenstar_c3/target.cpp | 2 +- variants/tenstar_c3/target.h | 2 +- variants/thinknode_m1/target.cpp | 2 +- variants/thinknode_m1/target.h | 2 +- variants/thinknode_m2/target.cpp | 2 +- variants/thinknode_m2/target.h | 2 +- variants/thinknode_m3/target.cpp | 2 +- variants/thinknode_m3/target.h | 2 +- variants/thinknode_m5/target.cpp | 2 +- variants/thinknode_m5/target.h | 2 +- variants/thinknode_m6/target.cpp | 2 +- variants/thinknode_m6/target.h | 2 +- variants/tiny_relay/target.cpp | 2 +- variants/tiny_relay/target.h | 2 +- variants/waveshare_rp2040_lora/target.cpp | 2 +- variants/waveshare_rp2040_lora/target.h | 2 +- variants/wio-e5-dev/target.cpp | 2 +- variants/wio-e5-dev/target.h | 2 +- variants/wio-e5-mini/target.cpp | 2 +- variants/wio-e5-mini/target.h | 2 +- variants/wio-tracker-l1/target.cpp | 2 +- variants/wio-tracker-l1/target.h | 2 +- variants/wio_wm1110/target.cpp | 2 +- variants/wio_wm1110/target.h | 2 +- variants/xiao_c3/target.cpp | 2 +- variants/xiao_c3/target.h | 2 +- variants/xiao_c6/XiaoC6Board.cpp | 2 +- variants/xiao_c6/target.h | 2 +- variants/xiao_nrf52/target.cpp | 2 +- variants/xiao_nrf52/target.h | 2 +- variants/xiao_rp2040/target.cpp | 2 +- variants/xiao_rp2040/target.h | 2 +- variants/xiao_s3_wio/target.cpp | 2 +- variants/xiao_s3_wio/target.h | 2 +- 141 files changed, 144 insertions(+), 144 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9bb747e791..f8e90be5e0 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -838,7 +838,7 @@ void MyMesh::begin(bool has_display) { _prefs.bw = constrain(_prefs.bw, 7.8f, 500.0f); _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); - _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); + _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, -9, MAX_LORA_TX_POWER); _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 62cd416422..d7ddd92a51 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -17,7 +17,7 @@ struct NodePrefs { // persisted to file uint8_t multi_acks; uint8_t manual_add_contacts; float bw; - uint8_t tx_power_dbm; + int8_t tx_power_dbm; uint8_t telemetry_mode_base; uint8_t telemetry_mode_loc; uint8_t telemetry_mode_env; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6d957cc094..8220ef0dbc 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -899,7 +899,7 @@ void MyMesh::dumpLogFile() { } } -void MyMesh::setTxPower(uint8_t power_dbm) { +void MyMesh::setTxPower(int8_t power_dbm) { radio_set_tx_power(power_dbm); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 0d5cd28a3d..7a51b4a97e 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -198,7 +198,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } void dumpLogFile() override; - void setTxPower(uint8_t power_dbm) override; + void setTxPower(int8_t power_dbm) override; void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; void formatStatsReply(char *reply) override; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 22a3d208b5..598b14de63 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -719,7 +719,7 @@ void MyMesh::dumpLogFile() { } } -void MyMesh::setTxPower(uint8_t power_dbm) { +void MyMesh::setTxPower(int8_t power_dbm) { radio_set_tx_power(power_dbm); } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index f470e55eb8..b4529e7762 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -188,7 +188,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { } void dumpLogFile() override; - void setTxPower(uint8_t power_dbm) override; + void setTxPower(int8_t power_dbm) override; void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 018ec2a20f..a389ec74bb 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -66,7 +66,7 @@ struct NodePrefs { // persisted to file char node_name[32]; double node_lat, node_lon; float freq; - uint8_t tx_power_dbm; + int8_t tx_power_dbm; uint8_t unused[3]; }; @@ -290,7 +290,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { } float getFreqPref() const { return _prefs.freq; } - uint8_t getTxPowerPref() const { return _prefs.tx_power_dbm; } + int8_t getTxPowerPref() const { return _prefs.tx_power_dbm; } void begin(FILESYSTEM& fs) { _fs = &fs; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 8e27323edd..f05fb245ce 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -815,7 +815,7 @@ void SensorMesh::updateFloodAdvertTimer() { } } -void SensorMesh::setTxPower(uint8_t power_dbm) { +void SensorMesh::setTxPower(int8_t power_dbm) { radio_set_tx_power(power_dbm); } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index ed35234582..4bc0d784ee 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -66,7 +66,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { void setLoggingOn(bool enable) override { } void eraseLogFile() override { } void dumpLogFile() override { } - void setTxPower(uint8_t power_dbm) override; + void setTxPower(int8_t power_dbm) override; void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 10ab866912..6dcf7018e1 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -92,7 +92,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f); _prefs->sf = constrain(_prefs->sf, 5, 12); _prefs->cr = constrain(_prefs->cr, 5, 8); - _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); + _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, -9, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f); @@ -326,7 +326,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } *reply = 0; // set null terminator } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { - sprintf(reply, "> %d", (uint32_t) _prefs->tx_power_dbm); + sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq)); } else if (memcmp(config, "public.key", 10) == 0) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 8661d1e6d8..146e1c6e20 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -19,7 +19,7 @@ struct NodePrefs { // persisted to file double node_lat, node_lon; char password[16]; float freq; - uint8_t tx_power_dbm; + int8_t tx_power_dbm; uint8_t disable_fwd; uint8_t advert_interval; // minutes / 2 uint8_t flood_advert_interval; // hours @@ -67,7 +67,7 @@ class CommonCLICallbacks { virtual void setLoggingOn(bool enable) = 0; virtual void eraseLogFile() = 0; virtual void dumpLogFile() = 0; - virtual void setTxPower(uint8_t power_dbm) = 0; + virtual void setTxPower(int8_t power_dbm) = 0; virtual void formatNeighborsReply(char *reply) = 0; virtual void removeNeighbor(const uint8_t* pubkey, int key_len) { // no op by default diff --git a/variants/ebyte_eora_s3/target.cpp b/variants/ebyte_eora_s3/target.cpp index 647f599726..501f560be8 100644 --- a/variants/ebyte_eora_s3/target.cpp +++ b/variants/ebyte_eora_s3/target.cpp @@ -75,7 +75,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/ebyte_eora_s3/target.h b/variants/ebyte_eora_s3/target.h index f184c7575a..892c3de3f3 100644 --- a/variants/ebyte_eora_s3/target.h +++ b/variants/ebyte_eora_s3/target.h @@ -25,5 +25,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/generic-e22/target.cpp b/variants/generic-e22/target.cpp index e02537798c..f76bb979ab 100644 --- a/variants/generic-e22/target.cpp +++ b/variants/generic-e22/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/generic-e22/target.h b/variants/generic-e22/target.h index 442706f33f..5ad13054f1 100644 --- a/variants/generic-e22/target.h +++ b/variants/generic-e22/target.h @@ -17,5 +17,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/generic_espnow/target.cpp b/variants/generic_espnow/target.cpp index 6b5d4e4446..f42085c041 100644 --- a/variants/generic_espnow/target.cpp +++ b/variants/generic_espnow/target.cpp @@ -25,7 +25,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { // no-op } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio_driver.setTxPower(dbm); } diff --git a/variants/generic_espnow/target.h b/variants/generic_espnow/target.h index 99b6f57786..1ebd0837b1 100644 --- a/variants/generic_espnow/target.h +++ b/variants/generic_espnow/target.h @@ -12,5 +12,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_ct62/target.cpp b/variants/heltec_ct62/target.cpp index a8c15f5ff4..5cc621a13a 100644 --- a/variants/heltec_ct62/target.cpp +++ b/variants/heltec_ct62/target.cpp @@ -27,7 +27,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_ct62/target.h b/variants/heltec_ct62/target.h index 9639ab2dfc..34130ae77c 100644 --- a/variants/heltec_ct62/target.h +++ b/variants/heltec_ct62/target.h @@ -16,5 +16,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_e213/target.cpp b/variants/heltec_e213/target.cpp index 23561850e1..c9233431cb 100644 --- a/variants/heltec_e213/target.cpp +++ b/variants/heltec_e213/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_e213/target.h b/variants/heltec_e213/target.h index 9ecdc212a7..14969c0f40 100644 --- a/variants/heltec_e213/target.h +++ b/variants/heltec_e213/target.h @@ -25,5 +25,5 @@ extern MomentaryButton user_btn; 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_e290/target.cpp b/variants/heltec_e290/target.cpp index 92b02092e6..b0c9630cf2 100644 --- a/variants/heltec_e290/target.cpp +++ b/variants/heltec_e290/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_e290/target.h b/variants/heltec_e290/target.h index 6077011272..5d423fc0dd 100644 --- a/variants/heltec_e290/target.h +++ b/variants/heltec_e290/target.h @@ -25,5 +25,5 @@ extern MomentaryButton user_btn; 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index ad79f71712..9852b68f8d 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h index e301a27351..f1921abfa3 100644 --- a/variants/heltec_mesh_solar/target.h +++ b/variants/heltec_mesh_solar/target.h @@ -42,5 +42,5 @@ extern SolarSensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index c3341103a5..160d00b6a3 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -36,7 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 6306cd699c..187675e979 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -50,5 +50,5 @@ extern T114SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_t190/target.cpp b/variants/heltec_t190/target.cpp index b93575943c..d22f8b8cfc 100644 --- a/variants/heltec_t190/target.cpp +++ b/variants/heltec_t190/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_t190/target.h b/variants/heltec_t190/target.h index 8a5fc716f5..83e0357075 100644 --- a/variants/heltec_t190/target.h +++ b/variants/heltec_t190/target.h @@ -25,5 +25,5 @@ extern MomentaryButton user_btn; 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 5ba9a8fb45..25c2634bb1 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -47,7 +47,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 23fab16e14..5296fb2c24 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -43,5 +43,5 @@ extern HWTSensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_tracker_v2/target.cpp b/variants/heltec_tracker_v2/target.cpp index da397fb741..c2e26b20d7 100644 --- a/variants/heltec_tracker_v2/target.cpp +++ b/variants/heltec_tracker_v2/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_tracker_v2/target.h b/variants/heltec_tracker_v2/target.h index 190404ef99..5b799e7809 100644 --- a/variants/heltec_tracker_v2/target.h +++ b/variants/heltec_tracker_v2/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_v2/target.cpp b/variants/heltec_v2/target.cpp index df71d3f4ba..c5a0475281 100644 --- a/variants/heltec_v2/target.cpp +++ b/variants/heltec_v2/target.cpp @@ -43,7 +43,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_v2/target.h b/variants/heltec_v2/target.h index 48d750be44..788dac7238 100644 --- a/variants/heltec_v2/target.h +++ b/variants/heltec_v2/target.h @@ -25,5 +25,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_v3/target.cpp b/variants/heltec_v3/target.cpp index 78b8819726..cdd2535e86 100644 --- a/variants/heltec_v3/target.cpp +++ b/variants/heltec_v3/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_v3/target.h b/variants/heltec_v3/target.h index 739aecfe0d..21a209f993 100644 --- a/variants/heltec_v3/target.h +++ b/variants/heltec_v3/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 0d2bd4976c..f971cc6085 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index 00d2adab6d..5016588d10 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -30,5 +30,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_wireless_paper/target.cpp b/variants/heltec_wireless_paper/target.cpp index dd2d51c0f4..06f548fc0e 100644 --- a/variants/heltec_wireless_paper/target.cpp +++ b/variants/heltec_wireless_paper/target.cpp @@ -43,7 +43,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_wireless_paper/target.h b/variants/heltec_wireless_paper/target.h index b89c486fb2..65739e7735 100644 --- a/variants/heltec_wireless_paper/target.h +++ b/variants/heltec_wireless_paper/target.h @@ -25,5 +25,5 @@ extern MomentaryButton user_btn; 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/ikoka_handheld_nrf/target.cpp b/variants/ikoka_handheld_nrf/target.cpp index efa6669f1f..48244e1722 100644 --- a/variants/ikoka_handheld_nrf/target.cpp +++ b/variants/ikoka_handheld_nrf/target.cpp @@ -36,7 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/ikoka_handheld_nrf/target.h b/variants/ikoka_handheld_nrf/target.h index a28ca81a55..d4af956e1c 100644 --- a/variants/ikoka_handheld_nrf/target.h +++ b/variants/ikoka_handheld_nrf/target.h @@ -25,5 +25,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_nano_nrf/target.cpp b/variants/ikoka_nano_nrf/target.cpp index aed591823b..be20cfb436 100644 --- a/variants/ikoka_nano_nrf/target.cpp +++ b/variants/ikoka_nano_nrf/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/ikoka_nano_nrf/target.h b/variants/ikoka_nano_nrf/target.h index 9b4e908ecb..7949ab6389 100644 --- a/variants/ikoka_nano_nrf/target.h +++ b/variants/ikoka_nano_nrf/target.h @@ -24,5 +24,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp index bd803399bf..4f6befc609 100644 --- a/variants/ikoka_stick_nrf/target.cpp +++ b/variants/ikoka_stick_nrf/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/ikoka_stick_nrf/target.h b/variants/ikoka_stick_nrf/target.h index c276e89f2d..fab825926b 100644 --- a/variants/ikoka_stick_nrf/target.h +++ b/variants/ikoka_stick_nrf/target.h @@ -24,5 +24,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/keepteen_lt1/target.cpp b/variants/keepteen_lt1/target.cpp index e72abf08b7..e2e183a709 100644 --- a/variants/keepteen_lt1/target.cpp +++ b/variants/keepteen_lt1/target.cpp @@ -40,7 +40,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/keepteen_lt1/target.h b/variants/keepteen_lt1/target.h index 0f1aa756c1..f2468d34ed 100644 --- a/variants/keepteen_lt1/target.h +++ b/variants/keepteen_lt1/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_t3s3/target.cpp b/variants/lilygo_t3s3/target.cpp index 1c7b3b0940..284811881c 100644 --- a/variants/lilygo_t3s3/target.cpp +++ b/variants/lilygo_t3s3/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_t3s3/target.h b/variants/lilygo_t3s3/target.h index f184c7575a..892c3de3f3 100644 --- a/variants/lilygo_t3s3/target.h +++ b/variants/lilygo_t3s3/target.h @@ -25,5 +25,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_t3s3_sx1276/target.cpp b/variants/lilygo_t3s3_sx1276/target.cpp index 042ff2062d..e7fe07a0c4 100644 --- a/variants/lilygo_t3s3_sx1276/target.cpp +++ b/variants/lilygo_t3s3_sx1276/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_t3s3_sx1276/target.h b/variants/lilygo_t3s3_sx1276/target.h index 98a0fe359f..2df4b3edb5 100644 --- a/variants/lilygo_t3s3_sx1276/target.h +++ b/variants/lilygo_t3s3_sx1276/target.h @@ -25,5 +25,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/lilygo_tbeam_1w/target.cpp b/variants/lilygo_tbeam_1w/target.cpp index fcdb42ed80..8cb6bdfa31 100644 --- a/variants/lilygo_tbeam_1w/target.cpp +++ b/variants/lilygo_tbeam_1w/target.cpp @@ -54,7 +54,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tbeam_1w/target.h b/variants/lilygo_tbeam_1w/target.h index 2c3e8970ad..99a750317d 100644 --- a/variants/lilygo_tbeam_1w/target.h +++ b/variants/lilygo_tbeam_1w/target.h @@ -23,5 +23,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp index a8caecb364..f85049d7c9 100644 --- a/variants/lilygo_tbeam_SX1262/target.cpp +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -45,7 +45,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tbeam_SX1262/target.h b/variants/lilygo_tbeam_SX1262/target.h index 5f33abb870..e5b3e445ec 100644 --- a/variants/lilygo_tbeam_SX1262/target.h +++ b/variants/lilygo_tbeam_SX1262/target.h @@ -25,5 +25,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tbeam_SX1276/target.cpp b/variants/lilygo_tbeam_SX1276/target.cpp index 0a7517a217..5fe82e111d 100644 --- a/variants/lilygo_tbeam_SX1276/target.cpp +++ b/variants/lilygo_tbeam_SX1276/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tbeam_SX1276/target.h b/variants/lilygo_tbeam_SX1276/target.h index b382b652de..cd4480dcec 100644 --- a/variants/lilygo_tbeam_SX1276/target.h +++ b/variants/lilygo_tbeam_SX1276/target.h @@ -25,5 +25,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 8ad306f1b4..6fec6f5831 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -42,7 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.h b/variants/lilygo_tbeam_supreme_SX1262/target.h index c6ffa0a66a..200a56905b 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.h +++ b/variants/lilygo_tbeam_supreme_SX1262/target.h @@ -23,5 +23,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/lilygo_tdeck/target.cpp b/variants/lilygo_tdeck/target.cpp index 50ffa7359a..731ecfd86b 100644 --- a/variants/lilygo_tdeck/target.cpp +++ b/variants/lilygo_tdeck/target.cpp @@ -45,7 +45,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tdeck/target.h b/variants/lilygo_tdeck/target.h index 4640925f1a..c31d0d0ff4 100644 --- a/variants/lilygo_tdeck/target.h +++ b/variants/lilygo_tdeck/target.h @@ -27,5 +27,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/lilygo_techo/target.cpp b/variants/lilygo_techo/target.cpp index 2ebc0641f5..12d222ff78 100644 --- a/variants/lilygo_techo/target.cpp +++ b/variants/lilygo_techo/target.cpp @@ -42,7 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_techo/target.h b/variants/lilygo_techo/target.h index 2b6ed45fca..d978d52244 100644 --- a/variants/lilygo_techo/target.h +++ b/variants/lilygo_techo/target.h @@ -27,5 +27,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_techo_lite/target.cpp b/variants/lilygo_techo_lite/target.cpp index 6979e34782..40a94526ed 100644 --- a/variants/lilygo_techo_lite/target.cpp +++ b/variants/lilygo_techo_lite/target.cpp @@ -41,7 +41,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_techo_lite/target.h b/variants/lilygo_techo_lite/target.h index 2b6ed45fca..d978d52244 100644 --- a/variants/lilygo_techo_lite/target.h +++ b/variants/lilygo_techo_lite/target.h @@ -27,5 +27,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tlora_c6/target.cpp b/variants/lilygo_tlora_c6/target.cpp index e12c58b5a8..3566fbe482 100644 --- a/variants/lilygo_tlora_c6/target.cpp +++ b/variants/lilygo_tlora_c6/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tlora_c6/target.h b/variants/lilygo_tlora_c6/target.h index c26d595866..1cb52fbc74 100644 --- a/variants/lilygo_tlora_c6/target.h +++ b/variants/lilygo_tlora_c6/target.h @@ -16,5 +16,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tlora_v2_1/target.cpp b/variants/lilygo_tlora_v2_1/target.cpp index 65a78c1940..ead62e7978 100644 --- a/variants/lilygo_tlora_v2_1/target.cpp +++ b/variants/lilygo_tlora_v2_1/target.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tlora_v2_1/target.h b/variants/lilygo_tlora_v2_1/target.h index 326a0dee73..cb7d861dd8 100644 --- a/variants/lilygo_tlora_v2_1/target.h +++ b/variants/lilygo_tlora_v2_1/target.h @@ -25,5 +25,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/mesh_pocket/target.cpp b/variants/mesh_pocket/target.cpp index a7f6c7fb14..6fabb31742 100644 --- a/variants/mesh_pocket/target.cpp +++ b/variants/mesh_pocket/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/mesh_pocket/target.h b/variants/mesh_pocket/target.h index 2aa9566936..6ab5d9c2f1 100644 --- a/variants/mesh_pocket/target.h +++ b/variants/mesh_pocket/target.h @@ -26,7 +26,7 @@ extern AutoDiscoverRTCClock rtc_clock; 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); extern SensorManager sensors; diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index 0e3b03f2b5..0edd440309 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -41,7 +41,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/meshadventurer/target.h b/variants/meshadventurer/target.h index 31bc406634..9d1ffca8d8 100644 --- a/variants/meshadventurer/target.h +++ b/variants/meshadventurer/target.h @@ -45,5 +45,5 @@ extern MASensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/meshtiny/target.cpp b/variants/meshtiny/target.cpp index 5fc60eae46..9188db1741 100644 --- a/variants/meshtiny/target.cpp +++ b/variants/meshtiny/target.cpp @@ -37,7 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/meshtiny/target.h b/variants/meshtiny/target.h index 8ee3ee86eb..31f8505d61 100644 --- a/variants/meshtiny/target.h +++ b/variants/meshtiny/target.h @@ -29,5 +29,5 @@ extern MomentaryButton back_btn; 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/minewsemi_me25ls01/target.cpp b/variants/minewsemi_me25ls01/target.cpp index 13306762ad..fcec194190 100644 --- a/variants/minewsemi_me25ls01/target.cpp +++ b/variants/minewsemi_me25ls01/target.cpp @@ -88,7 +88,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/minewsemi_me25ls01/target.h b/variants/minewsemi_me25ls01/target.h index a5da58234e..ea7383e254 100644 --- a/variants/minewsemi_me25ls01/target.h +++ b/variants/minewsemi_me25ls01/target.h @@ -25,5 +25,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index 81e7744fc0..aad10c5050 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -36,7 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/nano_g2_ultra/target.h b/variants/nano_g2_ultra/target.h index 3e58b90073..6e35412759 100644 --- a/variants/nano_g2_ultra/target.h +++ b/variants/nano_g2_ultra/target.h @@ -45,5 +45,5 @@ extern MomentaryButton user_btn; 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/nibble_screen_connect/target.cpp b/variants/nibble_screen_connect/target.cpp index 1980e0394c..6edaaad7ac 100644 --- a/variants/nibble_screen_connect/target.cpp +++ b/variants/nibble_screen_connect/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/nibble_screen_connect/target.h b/variants/nibble_screen_connect/target.h index 66e69901a0..f31efb8da5 100644 --- a/variants/nibble_screen_connect/target.h +++ b/variants/nibble_screen_connect/target.h @@ -25,6 +25,6 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index b26320e472..61eab91c29 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -40,7 +40,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/promicro/target.h b/variants/promicro/target.h index 38c4b4e883..d379927ecc 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak11310/target.cpp b/variants/rak11310/target.cpp index dba5bff2c4..67432998a0 100644 --- a/variants/rak11310/target.cpp +++ b/variants/rak11310/target.cpp @@ -29,7 +29,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak11310/target.h b/variants/rak11310/target.h index fe45c3f2c7..7c25cd9010 100644 --- a/variants/rak11310/target.h +++ b/variants/rak11310/target.h @@ -16,5 +16,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak3112/target.cpp b/variants/rak3112/target.cpp index 634573b81e..6cddfce519 100644 --- a/variants/rak3112/target.cpp +++ b/variants/rak3112/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak3112/target.h b/variants/rak3112/target.h index eae90900df..e7d85de9b8 100644 --- a/variants/rak3112/target.h +++ b/variants/rak3112/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp index 52f3a3d591..ec4fc28c00 100644 --- a/variants/rak3401/target.cpp +++ b/variants/rak3401/target.cpp @@ -48,7 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak3401/target.h b/variants/rak3401/target.h index 32f17cd1da..bb7f5dc490 100644 --- a/variants/rak3401/target.h +++ b/variants/rak3401/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak3x72/target.cpp b/variants/rak3x72/target.cpp index 446783aa43..48e7f42284 100644 --- a/variants/rak3x72/target.cpp +++ b/variants/rak3x72/target.cpp @@ -66,7 +66,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak3x72/target.h b/variants/rak3x72/target.h index e0c1441e7b..3ba1cf421d 100644 --- a/variants/rak3x72/target.h +++ b/variants/rak3x72/target.h @@ -52,5 +52,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index bc7465fda4..ea6a2bd4a5 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -48,7 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak4631/target.h b/variants/rak4631/target.h index aa6be664b3..eeb3e0947f 100644 --- a/variants/rak4631/target.h +++ b/variants/rak4631/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak_wismesh_tag/target.cpp b/variants/rak_wismesh_tag/target.cpp index 2bd30864b6..9646375e60 100644 --- a/variants/rak_wismesh_tag/target.cpp +++ b/variants/rak_wismesh_tag/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak_wismesh_tag/target.h b/variants/rak_wismesh_tag/target.h index 150d083199..a51b30924b 100644 --- a/variants/rak_wismesh_tag/target.h +++ b/variants/rak_wismesh_tag/target.h @@ -23,5 +23,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rpi_picow/target.cpp b/variants/rpi_picow/target.cpp index abb1485dfd..e3d4bf0900 100644 --- a/variants/rpi_picow/target.cpp +++ b/variants/rpi_picow/target.cpp @@ -29,7 +29,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rpi_picow/target.h b/variants/rpi_picow/target.h index 17dbb35fde..706578a403 100644 --- a/variants/rpi_picow/target.h +++ b/variants/rpi_picow/target.h @@ -16,5 +16,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp index efdaac6109..6674c180d8 100644 --- a/variants/sensecap_indicator-espnow/target.cpp +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -37,7 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { // no-op } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio_driver.setTxPower(dbm); } diff --git a/variants/sensecap_indicator-espnow/target.h b/variants/sensecap_indicator-espnow/target.h index bb78e9233b..a56dec7bd6 100644 --- a/variants/sensecap_indicator-espnow/target.h +++ b/variants/sensecap_indicator-espnow/target.h @@ -25,5 +25,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/sensecap_solar/target.cpp b/variants/sensecap_solar/target.cpp index 6bd7d31a7d..2c2ff0dc70 100644 --- a/variants/sensecap_solar/target.cpp +++ b/variants/sensecap_solar/target.cpp @@ -29,7 +29,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/sensecap_solar/target.h b/variants/sensecap_solar/target.h index 90d60ba523..f4a9880136 100644 --- a/variants/sensecap_solar/target.h +++ b/variants/sensecap_solar/target.h @@ -17,5 +17,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 3f0c1404de..026b25de72 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -51,7 +51,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/station_g2/target.h b/variants/station_g2/target.h index 2bf7016d4e..01428d586a 100644 --- a/variants/station_g2/target.h +++ b/variants/station_g2/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 82d958b5d4..da8fa48bb3 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -85,7 +85,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index 27351b9450..d4e3c02c52 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -43,5 +43,5 @@ extern T1000SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/tenstar_c3/target.cpp b/variants/tenstar_c3/target.cpp index a29780f093..d4f189b52f 100644 --- a/variants/tenstar_c3/target.cpp +++ b/variants/tenstar_c3/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/tenstar_c3/target.h b/variants/tenstar_c3/target.h index fa29e52bdb..e503564b39 100644 --- a/variants/tenstar_c3/target.h +++ b/variants/tenstar_c3/target.h @@ -17,5 +17,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index c3b1abc21e..ec2438d404 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -35,7 +35,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index 8425369d8f..92661d0964 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -45,5 +45,5 @@ extern ThinkNodeM1SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m2/target.cpp b/variants/thinknode_m2/target.cpp index cb3c1624d0..e7e36d05f1 100644 --- a/variants/thinknode_m2/target.cpp +++ b/variants/thinknode_m2/target.cpp @@ -46,7 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m2/target.h b/variants/thinknode_m2/target.h index b05def8aa4..77ebbfde86 100644 --- a/variants/thinknode_m2/target.h +++ b/variants/thinknode_m2/target.h @@ -26,7 +26,7 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp index 91d186dc1b..ca2b0aa067 100644 --- a/variants/thinknode_m3/target.cpp +++ b/variants/thinknode_m3/target.cpp @@ -89,7 +89,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m3/target.h b/variants/thinknode_m3/target.h index 23e9958146..4124761cfb 100644 --- a/variants/thinknode_m3/target.h +++ b/variants/thinknode_m3/target.h @@ -25,5 +25,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index 8208d2c43f..a7a049ef4f 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -53,7 +53,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 2af42095cf..a228cc9f56 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -29,7 +29,7 @@ extern PCA9557 expander; 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/thinknode_m6/target.cpp b/variants/thinknode_m6/target.cpp index c14dd300f8..36ca861805 100644 --- a/variants/thinknode_m6/target.cpp +++ b/variants/thinknode_m6/target.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m6/target.h b/variants/thinknode_m6/target.h index 38b1fed1b6..fb129988f0 100644 --- a/variants/thinknode_m6/target.h +++ b/variants/thinknode_m6/target.h @@ -27,5 +27,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/tiny_relay/target.cpp b/variants/tiny_relay/target.cpp index f738ac1795..313dfaa941 100644 --- a/variants/tiny_relay/target.cpp +++ b/variants/tiny_relay/target.cpp @@ -70,7 +70,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/tiny_relay/target.h b/variants/tiny_relay/target.h index 82747cdcc8..d158371210 100644 --- a/variants/tiny_relay/target.h +++ b/variants/tiny_relay/target.h @@ -55,5 +55,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/waveshare_rp2040_lora/target.cpp b/variants/waveshare_rp2040_lora/target.cpp index 7bc1d0430b..a9121b0c35 100644 --- a/variants/waveshare_rp2040_lora/target.cpp +++ b/variants/waveshare_rp2040_lora/target.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/waveshare_rp2040_lora/target.h b/variants/waveshare_rp2040_lora/target.h index aed5589393..fe1903de13 100644 --- a/variants/waveshare_rp2040_lora/target.h +++ b/variants/waveshare_rp2040_lora/target.h @@ -17,5 +17,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/wio-e5-dev/target.cpp b/variants/wio-e5-dev/target.cpp index 42e900e400..3e59b6cee7 100644 --- a/variants/wio-e5-dev/target.cpp +++ b/variants/wio-e5-dev/target.cpp @@ -63,7 +63,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/wio-e5-dev/target.h b/variants/wio-e5-dev/target.h index 5fdd0abafe..1d1fc5cb9a 100644 --- a/variants/wio-e5-dev/target.h +++ b/variants/wio-e5-dev/target.h @@ -29,5 +29,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/wio-e5-mini/target.cpp b/variants/wio-e5-mini/target.cpp index 0e2358b828..2e95ad6d1f 100644 --- a/variants/wio-e5-mini/target.cpp +++ b/variants/wio-e5-mini/target.cpp @@ -61,7 +61,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/wio-e5-mini/target.h b/variants/wio-e5-mini/target.h index 921c38d340..a4e5fb60c2 100644 --- a/variants/wio-e5-mini/target.h +++ b/variants/wio-e5-mini/target.h @@ -60,5 +60,5 @@ extern WIOE5SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 64866de004..4575a76c85 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index 97e575d896..e234764730 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -33,5 +33,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/wio_wm1110/target.cpp b/variants/wio_wm1110/target.cpp index c659d708bb..457d5bda2c 100644 --- a/variants/wio_wm1110/target.cpp +++ b/variants/wio_wm1110/target.cpp @@ -81,7 +81,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/wio_wm1110/target.h b/variants/wio_wm1110/target.h index 9bd4a22b80..8712a0ef0b 100644 --- a/variants/wio_wm1110/target.h +++ b/variants/wio_wm1110/target.h @@ -16,6 +16,6 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_c3/target.cpp b/variants/xiao_c3/target.cpp index fe3f7196a0..f8ee3d92c9 100644 --- a/variants/xiao_c3/target.cpp +++ b/variants/xiao_c3/target.cpp @@ -46,7 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_c3/target.h b/variants/xiao_c3/target.h index a7ef442180..57e3b81cab 100644 --- a/variants/xiao_c3/target.h +++ b/variants/xiao_c3/target.h @@ -16,5 +16,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_c6/XiaoC6Board.cpp b/variants/xiao_c6/XiaoC6Board.cpp index 555fed6295..5710c4ccb6 100644 --- a/variants/xiao_c6/XiaoC6Board.cpp +++ b/variants/xiao_c6/XiaoC6Board.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_c6/target.h b/variants/xiao_c6/target.h index 0fbb0bb2ec..28b465383a 100644 --- a/variants/xiao_c6/target.h +++ b/variants/xiao_c6/target.h @@ -17,5 +17,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index c9c02d215d..a8f4162eab 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_nrf52/target.h b/variants/xiao_nrf52/target.h index e1ea2a6b88..f4076c3496 100644 --- a/variants/xiao_nrf52/target.h +++ b/variants/xiao_nrf52/target.h @@ -22,5 +22,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_rp2040/target.cpp b/variants/xiao_rp2040/target.cpp index b7c1997580..6c9a91434b 100644 --- a/variants/xiao_rp2040/target.cpp +++ b/variants/xiao_rp2040/target.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_rp2040/target.h b/variants/xiao_rp2040/target.h index 33b3766c24..528c444147 100644 --- a/variants/xiao_rp2040/target.h +++ b/variants/xiao_rp2040/target.h @@ -17,5 +17,5 @@ extern SensorManager 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 26cd27ac2a..50981ab64a 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -46,7 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index c3227368ca..fffd16833f 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -26,5 +26,5 @@ 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); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); From 0b1fd580f12c1d16538ea0648efbe3cb7b7ebafc Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Fri, 6 Feb 2026 11:35:05 +0100 Subject: [PATCH 325/409] Fix double claim, eliminate dead code at compile time --- src/helpers/ui/SSD1306Display.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index f585feb07d..464b2642a0 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -19,11 +19,9 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { if (!_isOn) { - if (_peripher_power) { - _peripher_power->claim(); - begin(); - } - _isOn = true; + if (_peripher_power) _peripher_power->claim(); + _isOn = true; // set before begin() to prevent double claim + if (_peripher_power) begin(); // re-init display after power was cut } display.ssd1306_command(SSD1306_DISPLAYON); } @@ -32,7 +30,9 @@ void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); if (_isOn) { if (_peripher_power) { - if (PIN_OLED_RESET >= 0) digitalWrite(PIN_OLED_RESET, LOW); +#if PIN_OLED_RESET >= 0 + digitalWrite(PIN_OLED_RESET, LOW); +#endif _peripher_power->release(); } _isOn = false; From 5dcc377b775dd2bf192521c97bcf04f05f4bbe04 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Fri, 6 Feb 2026 11:07:10 +0100 Subject: [PATCH 326/409] Rewrite KISS modem to be fully spec-compliant --- docs/kiss_modem_protocol.md | 239 ++++++++++-------- examples/kiss_modem/KissModem.cpp | 375 ++++++++++++++++++----------- examples/kiss_modem/KissModem.h | 170 ++++++++----- examples/kiss_modem/main.cpp | 54 ++--- variants/xiao_nrf52/platformio.ini | 11 +- 5 files changed, 515 insertions(+), 334 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 00b0bf90f8..e042053c63 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -1,6 +1,6 @@ # MeshCore KISS Modem Protocol -Serial protocol for the KISS modem firmware. Enables sending/receiving MeshCore packets over LoRa and cryptographic operations using the modem's identity. +Standard KISS TNC firmware for MeshCore LoRa radios. Compatible with any KISS client (Direwolf, APRSdroid, YAAC, etc.) for sending and receiving raw packets. MeshCore-specific extensions (cryptography, radio configuration, telemetry) are available through the standard SetHardware (0x06) command. ## Serial Configuration @@ -8,7 +8,7 @@ Serial protocol for the KISS modem firmware. Enables sending/receiving MeshCore ## Frame Format -Standard KISS framing with byte stuffing. +Standard KISS framing per the KA9Q/K3MC specification. | Byte | Name | Description | |------|------|-------------| @@ -18,89 +18,146 @@ Standard KISS framing with byte stuffing. | `0xDD` | TFESC | Escaped FESC (FESC + TFESC = 0xDB) | ``` -┌──────┬─────────┬──────────────┬──────┐ -│ FEND │ Command │ Data (escaped)│ FEND │ -│ 0xC0 │ 1 byte │ 0-510 bytes │ 0xC0 │ -└──────┴─────────┴──────────────┴──────┘ +┌──────┬───────────┬──────────────┬──────┐ +│ FEND │ Type Byte │ Data (escaped)│ FEND │ +│ 0xC0 │ 1 byte │ 0-510 bytes │ 0xC0 │ +└──────┴───────────┴──────────────┴──────┘ ``` +### Type Byte + +The type byte is split into two nibbles: + +| Bits | Field | Description | +|------|-------|-------------| +| 7-4 | Port | Port number (0 for single-port TNC) | +| 3-0 | Command | Command number | + Maximum unescaped frame size: 512 bytes. -## Commands - -### Request Commands (Host → Modem) - -| Command | Value | Data | -|---------|-------|------| -| `CMD_DATA` | `0x00` | Packet (2-255 bytes) | -| `CMD_GET_IDENTITY` | `0x01` | - | -| `CMD_GET_RANDOM` | `0x02` | Length (1 byte, 1-64) | -| `CMD_VERIFY_SIGNATURE` | `0x03` | PubKey (32) + Signature (64) + Data | -| `CMD_SIGN_DATA` | `0x04` | Data to sign | -| `CMD_ENCRYPT_DATA` | `0x05` | Key (32) + Plaintext | -| `CMD_DECRYPT_DATA` | `0x06` | Key (32) + MAC (2) + Ciphertext | -| `CMD_KEY_EXCHANGE` | `0x07` | Remote PubKey (32) | -| `CMD_HASH` | `0x08` | Data to hash | -| `CMD_SET_RADIO` | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | -| `CMD_SET_TX_POWER` | `0x0A` | Power dBm (1) | -| *reserved* | `0x0B` | *(not implemented)* | -| `CMD_GET_RADIO` | `0x0C` | - | -| `CMD_GET_TX_POWER` | `0x0D` | - | -| *reserved* | `0x0E` | *(not implemented)* | -| `CMD_GET_VERSION` | `0x0F` | - | -| `CMD_GET_CURRENT_RSSI` | `0x10` | - | -| `CMD_IS_CHANNEL_BUSY` | `0x11` | - | -| `CMD_GET_AIRTIME` | `0x12` | Packet length (1) | -| `CMD_GET_NOISE_FLOOR` | `0x13` | - | -| `CMD_GET_STATS` | `0x14` | - | -| `CMD_GET_BATTERY` | `0x15` | - | -| `CMD_PING` | `0x16` | - | -| `CMD_GET_SENSORS` | `0x17` | Permissions (1) | - -### Response Commands (Modem → Host) - -| Command | Value | Data | -|---------|-------|------| -| `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet | -| `RESP_IDENTITY` | `0x21` | PubKey (32) | -| `RESP_RANDOM` | `0x22` | Random bytes (1-64) | -| `RESP_VERIFY` | `0x23` | Result (1): 0x00=invalid, 0x01=valid | -| `RESP_SIGNATURE` | `0x24` | Signature (64) | -| `RESP_ENCRYPTED` | `0x25` | MAC (2) + Ciphertext | -| `RESP_DECRYPTED` | `0x26` | Plaintext | -| `RESP_SHARED_SECRET` | `0x27` | Shared secret (32) | -| `RESP_HASH` | `0x28` | SHA-256 hash (32) | -| `RESP_OK` | `0x29` | - | -| `RESP_RADIO` | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | -| `RESP_TX_POWER` | `0x2B` | Power dBm (1) | -| *reserved* | `0x2C` | *(not implemented)* | -| `RESP_VERSION` | `0x2D` | Version (1) + Reserved (1) | -| `RESP_ERROR` | `0x2E` | Error code (1) | -| `RESP_TX_DONE` | `0x2F` | Result (1): 0x00=failed, 0x01=success | -| `RESP_CURRENT_RSSI` | `0x30` | RSSI dBm (1, signed) | -| `RESP_CHANNEL_BUSY` | `0x31` | Result (1): 0x00=clear, 0x01=busy | -| `RESP_AIRTIME` | `0x32` | Milliseconds (4) | -| `RESP_NOISE_FLOOR` | `0x33` | dBm (2, signed) | -| `RESP_STATS` | `0x34` | RX (4) + TX (4) + Errors (4) | -| `RESP_BATTERY` | `0x35` | Millivolts (2) | -| `RESP_PONG` | `0x36` | - | -| `RESP_SENSORS` | `0x37` | CayenneLPP payload | - -## Error Codes +## Standard KISS Commands + +### Host to TNC + +| Command | Value | Data | Description | +|---------|-------|------|-------------| +| Data | `0x00` | Raw packet | Queue packet for transmission | +| TXDELAY | `0x01` | Delay (1 byte) | Transmitter keyup delay in 10ms units (default: 50 = 500ms) | +| Persistence | `0x02` | P (1 byte) | CSMA persistence parameter 0-255 (default: 63) | +| SlotTime | `0x03` | Interval (1 byte) | CSMA slot interval in 10ms units (default: 10 = 100ms) | +| TXtail | `0x04` | Delay (1 byte) | Post-TX hold time in 10ms units (default: 0) | +| FullDuplex | `0x05` | Mode (1 byte) | 0 = half duplex, nonzero = full duplex (default: 0) | +| SetHardware | `0x06` | Sub-command + data | MeshCore extensions (see below) | +| Return | `0xFF` | - | Exit KISS mode (no-op) | + +### TNC to Host + +| Type | Value | Data | Description | +|------|-------|------|-------------| +| Data | `0x00` | Raw packet | Received packet from radio | + +Data frames carry raw packet data only, with no metadata prepended. + +### CSMA Behavior + +The TNC implements p-persistent CSMA for half-duplex operation: + +1. When a packet is queued, monitor carrier detect +2. When the channel clears, generate a random value 0-255 +3. If the value is less than or equal to P (Persistence), wait TXDELAY then transmit +4. Otherwise, wait SlotTime and repeat from step 1 + +In full-duplex mode, CSMA is bypassed and packets transmit after TXDELAY. + +## SetHardware Extensions (0x06) + +MeshCore-specific functionality uses the standard KISS SetHardware command. The first byte of SetHardware data is a sub-command. Standard KISS clients ignore these frames. + +### Frame Format + +``` +┌──────┬──────┬─────────────┬──────────────┬──────┐ +│ FEND │ 0x06 │ Sub-command │ Data (escaped)│ FEND │ +│ 0xC0 │ │ 1 byte │ variable │ 0xC0 │ +└──────┴──────┴─────────────┴──────────────┴──────┘ +``` + +### Request Sub-commands (Host to TNC) + +| Sub-command | Value | Data | +|-------------|-------|------| +| GetIdentity | `0x01` | - | +| GetRandom | `0x02` | Length (1 byte, 1-64) | +| VerifySignature | `0x03` | PubKey (32) + Signature (64) + Data | +| SignData | `0x04` | Data to sign | +| EncryptData | `0x05` | Key (32) + Plaintext | +| DecryptData | `0x06` | Key (32) + MAC (2) + Ciphertext | +| KeyExchange | `0x07` | Remote PubKey (32) | +| Hash | `0x08` | Data to hash | +| SetRadio | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | +| SetTxPower | `0x0A` | Power dBm (1) | +| GetRadio | `0x0C` | - | +| GetTxPower | `0x0D` | - | +| GetVersion | `0x0F` | - | +| GetCurrentRssi | `0x10` | - | +| IsChannelBusy | `0x11` | - | +| GetAirtime | `0x12` | Packet length (1) | +| GetNoiseFloor | `0x13` | - | +| GetStats | `0x14` | - | +| GetBattery | `0x15` | - | +| Ping | `0x16` | - | +| GetSensors | `0x17` | Permissions (1) | + +### Response Sub-commands (TNC to Host) + +| Sub-command | Value | Data | +|-------------|-------|------| +| Identity | `0x21` | PubKey (32) | +| Random | `0x22` | Random bytes (1-64) | +| Verify | `0x23` | Result (1): 0x00=invalid, 0x01=valid | +| Signature | `0x24` | Signature (64) | +| Encrypted | `0x25` | MAC (2) + Ciphertext | +| Decrypted | `0x26` | Plaintext | +| SharedSecret | `0x27` | Shared secret (32) | +| Hash | `0x28` | SHA-256 hash (32) | +| OK | `0x29` | - | +| Radio | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | +| TxPower | `0x2B` | Power dBm (1) | +| Version | `0x2D` | Version (1) + Reserved (1) | +| Error | `0x2E` | Error code (1) | +| TxDone | `0x2F` | Result (1): 0x00=failed, 0x01=success | +| CurrentRssi | `0x30` | RSSI dBm (1, signed) | +| ChannelBusy | `0x31` | Result (1): 0x00=clear, 0x01=busy | +| Airtime | `0x32` | Milliseconds (4) | +| NoiseFloor | `0x33` | dBm (2, signed) | +| Stats | `0x34` | RX (4) + TX (4) + Errors (4) | +| Battery | `0x35` | Millivolts (2) | +| Pong | `0x36` | - | +| Sensors | `0x37` | CayenneLPP payload | +| RxMeta | `0x38` | SNR (1) + RSSI (1) | + +### Error Codes | Code | Value | Description | |------|-------|-------------| -| `ERR_INVALID_LENGTH` | `0x01` | Request data too short | -| `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value | -| `ERR_NO_CALLBACK` | `0x03` | Feature not available | -| `ERR_MAC_FAILED` | `0x04` | MAC verification failed | -| `ERR_UNKNOWN_CMD` | `0x05` | Unknown command | -| `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed | -| `ERR_TX_PENDING` | `0x07` | TX already pending | +| InvalidLength | `0x01` | Request data too short | +| InvalidParam | `0x02` | Invalid parameter value | +| NoCallback | `0x03` | Feature not available | +| MacFailed | `0x04` | MAC verification failed | +| UnknownCmd | `0x05` | Unknown sub-command | +| EncryptFailed | `0x06` | Encryption failed | + +### Unsolicited Events + +The TNC sends these SetHardware frames without a preceding request: + +**TxDone (0x2F)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. + +**RxMeta (0x38)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Standard KISS clients ignore this frame. ## Data Formats -### Radio Parameters (CMD_SET_RADIO / RESP_RADIO) +### Radio Parameters (SetRadio / Radio response) All values little-endian. @@ -111,27 +168,9 @@ All values little-endian. | SF | 1 byte | Spreading factor (5-12) | | CR | 1 byte | Coding rate (5-8) | -### Received Packet (CMD_DATA response) - -| Field | Size | Description | -|-------|------|-------------| -| SNR | 1 byte | Signal-to-noise × 4, signed | -| RSSI | 1 byte | Signal strength dBm, signed | -| Packet | variable | Raw MeshCore packet | - -### Noise Floor (RESP_NOISE_FLOOR) - -Response to `CMD_GET_NOISE_FLOOR` (0x13). Little-endian. +### Stats (Stats response) -| Field | Size | Description | -|--------------|------|--------------------------------| -| Noise floor | 2 | int16_t, dBm (signed), e.g. -120 | - -The modem recalibrates the noise floor every two seconds with an AGC reset every 30 seconds. - -### Stats (RESP_STATS) - -Response to `CMD_GET_STATS` (0x14). All values little-endian. +All values little-endian. | Field | Size | Description | |-------|------|-------------| @@ -139,7 +178,7 @@ Response to `CMD_GET_STATS` (0x14). All values little-endian. | TX | 4 bytes | Packets transmitted | | Errors | 4 bytes | Receive errors | -### Sensor Permissions (CMD_GET_SENSORS) +### Sensor Permissions (GetSensors) | Bit | Value | Description | |-----|-------|-------------| @@ -149,14 +188,14 @@ Response to `CMD_GET_STATS` (0x14). All values little-endian. Use `0x07` for all permissions. -### Sensor Data (RESP_SENSORS) +### Sensor Data (Sensors response) Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) for parsing. ## Notes - Modem generates identity on first boot (stored in flash) -- SNR values multiplied by 4 for 0.25 dB precision -- Wait for `RESP_TX_DONE` before sending next packet -- Sending `CMD_DATA` while TX is pending returns `ERR_TX_PENDING` +- SNR values in RxMeta are multiplied by 4 for 0.25 dB precision +- TxDone is sent as a SetHardware event after each transmission +- Standard KISS clients receive only type 0x00 data frames and can safely ignore all SetHardware (0x06) frames - See [packet_structure.md](./packet_structure.md) for packet format diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index d9c71bf859..9915ec5ec2 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -9,10 +9,20 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _rx_active = false; _has_pending_tx = false; _pending_tx_len = 0; + _txdelay = KISS_DEFAULT_TXDELAY; + _persistence = KISS_DEFAULT_PERSISTENCE; + _slottime = KISS_DEFAULT_SLOTTIME; + _txtail = 0; + _fullduplex = 0; + _tx_state = TX_IDLE; + _tx_timer = 0; _setRadioCallback = nullptr; _setTxPowerCallback = nullptr; _getCurrentRssiCallback = nullptr; _getStatsCallback = nullptr; + _sendPacketCallback = nullptr; + _isSendCompleteCallback = nullptr; + _onSendFinishedCallback = nullptr; _config = {0, 0, 0, 0, 0}; } @@ -21,6 +31,7 @@ void KissModem::begin() { _rx_escaped = false; _rx_active = false; _has_pending_tx = false; + _tx_state = TX_IDLE; } void KissModem::writeByte(uint8_t b) { @@ -35,23 +46,33 @@ void KissModem::writeByte(uint8_t b) { } } -void KissModem::writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len) { +void KissModem::writeFrame(uint8_t type, const uint8_t* data, uint16_t len) { _serial.write(KISS_FEND); - writeByte(cmd); + writeByte(type); for (uint16_t i = 0; i < len; i++) { writeByte(data[i]); } _serial.write(KISS_FEND); } -void KissModem::writeErrorFrame(uint8_t error_code) { - writeFrame(RESP_ERROR, &error_code, 1); +void KissModem::writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len) { + _serial.write(KISS_FEND); + writeByte(KISS_CMD_SETHARDWARE); + writeByte(sub_cmd); + for (uint16_t i = 0; i < len; i++) { + writeByte(data[i]); + } + _serial.write(KISS_FEND); +} + +void KissModem::writeHardwareError(uint8_t error_code) { + writeHardwareFrame(HW_RESP_ERROR, &error_code, 1); } void KissModem::loop() { while (_serial.available()) { uint8_t b = _serial.read(); - + if (b == KISS_FEND) { if (_rx_active && _rx_len > 0) { processFrame(); @@ -61,283 +82,368 @@ void KissModem::loop() { _rx_active = true; continue; } - + if (!_rx_active) continue; - + if (b == KISS_FESC) { _rx_escaped = true; continue; } - + if (_rx_escaped) { _rx_escaped = false; if (b == KISS_TFEND) b = KISS_FEND; else if (b == KISS_TFESC) b = KISS_FESC; + else continue; } - + if (_rx_len < KISS_MAX_FRAME_SIZE) { _rx_buf[_rx_len++] = b; } } + + processTx(); } void KissModem::processFrame() { if (_rx_len < 1) return; - - uint8_t cmd = _rx_buf[0]; + + uint8_t type_byte = _rx_buf[0]; + + if (type_byte == KISS_CMD_RETURN) return; + + uint8_t port = (type_byte >> 4) & 0x0F; + uint8_t cmd = type_byte & 0x0F; + + if (port != 0) return; + const uint8_t* data = &_rx_buf[1]; uint16_t data_len = _rx_len - 1; - + switch (cmd) { - case CMD_DATA: - if (data_len < 2) { - writeErrorFrame(ERR_INVALID_LENGTH); - } else if (data_len > KISS_MAX_PACKET_SIZE) { - writeErrorFrame(ERR_INVALID_LENGTH); - } else if (_has_pending_tx) { - writeErrorFrame(ERR_TX_PENDING); - } else { + case KISS_CMD_DATA: + if (data_len > 0 && data_len <= KISS_MAX_PACKET_SIZE && !_has_pending_tx) { memcpy(_pending_tx, data, data_len); _pending_tx_len = data_len; _has_pending_tx = true; } break; - case CMD_GET_IDENTITY: + + case KISS_CMD_TXDELAY: + if (data_len >= 1) _txdelay = data[0]; + break; + + case KISS_CMD_PERSISTENCE: + if (data_len >= 1) _persistence = data[0]; + break; + + case KISS_CMD_SLOTTIME: + if (data_len >= 1) _slottime = data[0]; + break; + + case KISS_CMD_TXTAIL: + if (data_len >= 1) _txtail = data[0]; + break; + + case KISS_CMD_FULLDUPLEX: + if (data_len >= 1) _fullduplex = data[0]; + break; + + case KISS_CMD_SETHARDWARE: + if (data_len >= 1) { + handleHardwareCommand(data[0], data + 1, data_len - 1); + } + break; + + default: + break; + } +} + +void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint16_t len) { + switch (sub_cmd) { + case HW_CMD_GET_IDENTITY: handleGetIdentity(); break; - case CMD_GET_RANDOM: - handleGetRandom(data, data_len); + case HW_CMD_GET_RANDOM: + handleGetRandom(data, len); break; - case CMD_VERIFY_SIGNATURE: - handleVerifySignature(data, data_len); + case HW_CMD_VERIFY_SIGNATURE: + handleVerifySignature(data, len); break; - case CMD_SIGN_DATA: - handleSignData(data, data_len); + case HW_CMD_SIGN_DATA: + handleSignData(data, len); break; - case CMD_ENCRYPT_DATA: - handleEncryptData(data, data_len); + case HW_CMD_ENCRYPT_DATA: + handleEncryptData(data, len); break; - case CMD_DECRYPT_DATA: - handleDecryptData(data, data_len); + case HW_CMD_DECRYPT_DATA: + handleDecryptData(data, len); break; - case CMD_KEY_EXCHANGE: - handleKeyExchange(data, data_len); + case HW_CMD_KEY_EXCHANGE: + handleKeyExchange(data, len); break; - case CMD_HASH: - handleHash(data, data_len); + case HW_CMD_HASH: + handleHash(data, len); break; - case CMD_SET_RADIO: - handleSetRadio(data, data_len); + case HW_CMD_SET_RADIO: + handleSetRadio(data, len); break; - case CMD_SET_TX_POWER: - handleSetTxPower(data, data_len); + case HW_CMD_SET_TX_POWER: + handleSetTxPower(data, len); break; - case CMD_GET_RADIO: + case HW_CMD_GET_RADIO: handleGetRadio(); break; - case CMD_GET_TX_POWER: + case HW_CMD_GET_TX_POWER: handleGetTxPower(); break; - case CMD_GET_VERSION: + case HW_CMD_GET_VERSION: handleGetVersion(); break; - case CMD_GET_CURRENT_RSSI: + case HW_CMD_GET_CURRENT_RSSI: handleGetCurrentRssi(); break; - case CMD_IS_CHANNEL_BUSY: + case HW_CMD_IS_CHANNEL_BUSY: handleIsChannelBusy(); break; - case CMD_GET_AIRTIME: - handleGetAirtime(data, data_len); + case HW_CMD_GET_AIRTIME: + handleGetAirtime(data, len); break; - case CMD_GET_NOISE_FLOOR: + case HW_CMD_GET_NOISE_FLOOR: handleGetNoiseFloor(); break; - case CMD_GET_STATS: + case HW_CMD_GET_STATS: handleGetStats(); break; - case CMD_GET_BATTERY: + case HW_CMD_GET_BATTERY: handleGetBattery(); break; - case CMD_PING: + case HW_CMD_PING: handlePing(); break; - case CMD_GET_SENSORS: - handleGetSensors(data, data_len); + case HW_CMD_GET_SENSORS: + handleGetSensors(data, len); break; default: - writeErrorFrame(ERR_UNKNOWN_CMD); + writeHardwareError(HW_ERR_UNKNOWN_CMD); + break; + } +} + +void KissModem::processTx() { + switch (_tx_state) { + case TX_IDLE: + if (_has_pending_tx) { + if (_fullduplex) { + _tx_timer = millis(); + _tx_state = TX_DELAY; + } else { + _tx_state = TX_WAIT_CLEAR; + } + } + break; + + case TX_WAIT_CLEAR: + if (!_radio.isReceiving()) { + uint8_t rand_val; + _rng.random(&rand_val, 1); + if (rand_val <= _persistence) { + _tx_timer = millis(); + _tx_state = TX_DELAY; + } else { + _tx_timer = millis(); + _tx_state = TX_SLOT_WAIT; + } + } + break; + + case TX_SLOT_WAIT: + if (millis() - _tx_timer >= (uint32_t)_slottime * 10) { + _tx_state = TX_WAIT_CLEAR; + } + break; + + case TX_DELAY: + if (millis() - _tx_timer >= (uint32_t)_txdelay * 10) { + if (_sendPacketCallback) { + _sendPacketCallback(_pending_tx, _pending_tx_len); + _tx_state = TX_SENDING; + } else { + _has_pending_tx = false; + _tx_state = TX_IDLE; + } + } + break; + + case TX_SENDING: + if (_isSendCompleteCallback && _isSendCompleteCallback()) { + if (_onSendFinishedCallback) _onSendFinishedCallback(); + uint8_t result = 0x01; + writeHardwareFrame(HW_RESP_TX_DONE, &result, 1); + _has_pending_tx = false; + _tx_state = TX_IDLE; + } break; } } +void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { + writeFrame(KISS_CMD_DATA, packet, len); + uint8_t meta[2] = { (uint8_t)snr, (uint8_t)rssi }; + writeHardwareFrame(HW_RESP_RX_META, meta, 2); +} + void KissModem::handleGetIdentity() { - writeFrame(RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); + writeHardwareFrame(HW_RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); } void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t requested = data[0]; if (requested < 1 || requested > 64) { - writeErrorFrame(ERR_INVALID_PARAM); + writeHardwareError(HW_ERR_INVALID_PARAM); return; } - + uint8_t buf[64]; _rng.random(buf, requested); - writeFrame(RESP_RANDOM, buf, requested); + writeHardwareFrame(HW_RESP_RANDOM, buf, requested); } void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE + SIGNATURE_SIZE + 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + mesh::Identity signer(data); const uint8_t* signature = data + PUB_KEY_SIZE; const uint8_t* msg = data + PUB_KEY_SIZE + SIGNATURE_SIZE; uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE; - + uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00; - writeFrame(RESP_VERIFY, &result, 1); + writeHardwareFrame(HW_RESP_VERIFY, &result, 1); } void KissModem::handleSignData(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t signature[SIGNATURE_SIZE]; _identity.sign(signature, data, len); - writeFrame(RESP_SIGNATURE, signature, SIGNATURE_SIZE); + writeHardwareFrame(HW_RESP_SIGNATURE, signature, SIGNATURE_SIZE); } void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE + 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + const uint8_t* key = data; const uint8_t* plaintext = data + PUB_KEY_SIZE; uint16_t plaintext_len = len - PUB_KEY_SIZE; - + uint8_t buf[KISS_MAX_FRAME_SIZE]; int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len); - + if (encrypted_len > 0) { - writeFrame(RESP_ENCRYPTED, buf, encrypted_len); + writeHardwareFrame(HW_RESP_ENCRYPTED, buf, encrypted_len); } else { - writeErrorFrame(ERR_ENCRYPT_FAILED); + writeHardwareError(HW_ERR_ENCRYPT_FAILED); } } void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE + CIPHER_MAC_SIZE + 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + const uint8_t* key = data; const uint8_t* ciphertext = data + PUB_KEY_SIZE; uint16_t ciphertext_len = len - PUB_KEY_SIZE; - + uint8_t buf[KISS_MAX_FRAME_SIZE]; int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len); - + if (decrypted_len > 0) { - writeFrame(RESP_DECRYPTED, buf, decrypted_len); + writeHardwareFrame(HW_RESP_DECRYPTED, buf, decrypted_len); } else { - writeErrorFrame(ERR_MAC_FAILED); + writeHardwareError(HW_ERR_MAC_FAILED); } } void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t shared_secret[PUB_KEY_SIZE]; _identity.calcSharedSecret(shared_secret, data); - writeFrame(RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); + writeHardwareFrame(HW_RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); } void KissModem::handleHash(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t hash[32]; mesh::Utils::sha256(hash, 32, data, len); - writeFrame(RESP_HASH, hash, 32); -} - -bool KissModem::getPacketToSend(uint8_t* packet, uint16_t* len) { - if (!_has_pending_tx) return false; - - memcpy(packet, _pending_tx, _pending_tx_len); - *len = _pending_tx_len; - _has_pending_tx = false; - return true; -} - -void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { - uint8_t buf[2 + KISS_MAX_PACKET_SIZE]; - buf[0] = (uint8_t)snr; - buf[1] = (uint8_t)rssi; - memcpy(&buf[2], packet, len); - writeFrame(CMD_DATA, buf, 2 + len); + writeHardwareFrame(HW_RESP_HASH, hash, 32); } void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { if (len < 10) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } if (!_setRadioCallback) { - writeErrorFrame(ERR_NO_CALLBACK); + writeHardwareError(HW_ERR_NO_CALLBACK); return; } - + uint32_t freq_hz, bw_hz; memcpy(&freq_hz, data, 4); memcpy(&bw_hz, data + 4, 4); uint8_t sf = data[8]; uint8_t cr = data[9]; - + _config.freq_hz = freq_hz; _config.bw_hz = bw_hz; _config.sf = sf; _config.cr = cr; - + float freq = freq_hz / 1000000.0f; float bw = bw_hz / 1000.0f; - + _setRadioCallback(freq, bw, sf, cr); - writeFrame(RESP_OK, nullptr, 0); + writeHardwareFrame(HW_RESP_OK, nullptr, 0); } void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } if (!_setTxPowerCallback) { - writeErrorFrame(ERR_NO_CALLBACK); + writeHardwareError(HW_ERR_NO_CALLBACK); return; } - + _config.tx_power = data[0]; _setTxPowerCallback(data[0]); - writeFrame(RESP_OK, nullptr, 0); + writeHardwareFrame(HW_RESP_OK, nullptr, 0); } void KissModem::handleGetRadio() { @@ -346,92 +452,87 @@ void KissModem::handleGetRadio() { memcpy(buf + 4, &_config.bw_hz, 4); buf[8] = _config.sf; buf[9] = _config.cr; - writeFrame(RESP_RADIO, buf, 10); + writeHardwareFrame(HW_RESP_RADIO, buf, 10); } void KissModem::handleGetTxPower() { - writeFrame(RESP_TX_POWER, &_config.tx_power, 1); + writeHardwareFrame(HW_RESP_TX_POWER, &_config.tx_power, 1); } void KissModem::handleGetVersion() { uint8_t buf[2]; buf[0] = KISS_FIRMWARE_VERSION; buf[1] = 0; - writeFrame(RESP_VERSION, buf, 2); -} - -void KissModem::onTxComplete(bool success) { - uint8_t result = success ? 0x01 : 0x00; - writeFrame(RESP_TX_DONE, &result, 1); + writeHardwareFrame(HW_RESP_VERSION, buf, 2); } void KissModem::handleGetCurrentRssi() { if (!_getCurrentRssiCallback) { - writeErrorFrame(ERR_NO_CALLBACK); + writeHardwareError(HW_ERR_NO_CALLBACK); return; } - + float rssi = _getCurrentRssiCallback(); int8_t rssi_byte = (int8_t)rssi; - writeFrame(RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); + writeHardwareFrame(HW_RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); } void KissModem::handleIsChannelBusy() { uint8_t busy = _radio.isReceiving() ? 0x01 : 0x00; - writeFrame(RESP_CHANNEL_BUSY, &busy, 1); + writeHardwareFrame(HW_RESP_CHANNEL_BUSY, &busy, 1); } void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t packet_len = data[0]; uint32_t airtime = _radio.getEstAirtimeFor(packet_len); - writeFrame(RESP_AIRTIME, (uint8_t*)&airtime, 4); + writeHardwareFrame(HW_RESP_AIRTIME, (uint8_t*)&airtime, 4); } void KissModem::handleGetNoiseFloor() { int16_t noise_floor = _radio.getNoiseFloor(); - writeFrame(RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); + writeHardwareFrame(HW_RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); } void KissModem::handleGetStats() { if (!_getStatsCallback) { - writeErrorFrame(ERR_NO_CALLBACK); + writeHardwareError(HW_ERR_NO_CALLBACK); return; } - + uint32_t rx, tx, errors; _getStatsCallback(&rx, &tx, &errors); uint8_t buf[12]; memcpy(buf, &rx, 4); memcpy(buf + 4, &tx, 4); memcpy(buf + 8, &errors, 4); - writeFrame(RESP_STATS, buf, 12); + writeHardwareFrame(HW_RESP_STATS, buf, 12); } void KissModem::handleGetBattery() { uint16_t mv = _board.getBattMilliVolts(); - writeFrame(RESP_BATTERY, (uint8_t*)&mv, 2); + writeHardwareFrame(HW_RESP_BATTERY, (uint8_t*)&mv, 2); } void KissModem::handlePing() { - writeFrame(RESP_PONG, nullptr, 0); + writeHardwareFrame(HW_RESP_PONG, nullptr, 0); } void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t permissions = data[0]; CayenneLPP telemetry(255); if (_sensors.querySensors(permissions, telemetry)) { - writeFrame(RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); + writeHardwareFrame(HW_RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); } else { - writeFrame(RESP_SENSORS, nullptr, 0); + writeHardwareFrame(HW_RESP_SENSORS, nullptr, 0); } } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 170bb0c2a4..98f3c30031 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -11,62 +11,74 @@ #define KISS_TFEND 0xDC #define KISS_TFESC 0xDD -#define KISS_MAX_FRAME_SIZE 512 +#define KISS_MAX_FRAME_SIZE 512 #define KISS_MAX_PACKET_SIZE 255 -#define CMD_DATA 0x00 -#define CMD_GET_IDENTITY 0x01 -#define CMD_GET_RANDOM 0x02 -#define CMD_VERIFY_SIGNATURE 0x03 -#define CMD_SIGN_DATA 0x04 -#define CMD_ENCRYPT_DATA 0x05 -#define CMD_DECRYPT_DATA 0x06 -#define CMD_KEY_EXCHANGE 0x07 -#define CMD_HASH 0x08 -#define CMD_SET_RADIO 0x09 -#define CMD_SET_TX_POWER 0x0A -#define CMD_GET_RADIO 0x0C -#define CMD_GET_TX_POWER 0x0D -#define CMD_GET_VERSION 0x0F -#define CMD_GET_CURRENT_RSSI 0x10 -#define CMD_IS_CHANNEL_BUSY 0x11 -#define CMD_GET_AIRTIME 0x12 -#define CMD_GET_NOISE_FLOOR 0x13 -#define CMD_GET_STATS 0x14 -#define CMD_GET_BATTERY 0x15 -#define CMD_PING 0x16 -#define CMD_GET_SENSORS 0x17 - -#define RESP_IDENTITY 0x21 -#define RESP_RANDOM 0x22 -#define RESP_VERIFY 0x23 -#define RESP_SIGNATURE 0x24 -#define RESP_ENCRYPTED 0x25 -#define RESP_DECRYPTED 0x26 -#define RESP_SHARED_SECRET 0x27 -#define RESP_HASH 0x28 -#define RESP_OK 0x29 -#define RESP_RADIO 0x2A -#define RESP_TX_POWER 0x2B -#define RESP_VERSION 0x2D -#define RESP_ERROR 0x2E -#define RESP_TX_DONE 0x2F -#define RESP_CURRENT_RSSI 0x30 -#define RESP_CHANNEL_BUSY 0x31 -#define RESP_AIRTIME 0x32 -#define RESP_NOISE_FLOOR 0x33 -#define RESP_STATS 0x34 -#define RESP_BATTERY 0x35 -#define RESP_PONG 0x36 -#define RESP_SENSORS 0x37 - -#define ERR_INVALID_LENGTH 0x01 -#define ERR_INVALID_PARAM 0x02 -#define ERR_NO_CALLBACK 0x03 -#define ERR_MAC_FAILED 0x04 -#define ERR_UNKNOWN_CMD 0x05 -#define ERR_ENCRYPT_FAILED 0x06 -#define ERR_TX_PENDING 0x07 +#define KISS_CMD_DATA 0x00 +#define KISS_CMD_TXDELAY 0x01 +#define KISS_CMD_PERSISTENCE 0x02 +#define KISS_CMD_SLOTTIME 0x03 +#define KISS_CMD_TXTAIL 0x04 +#define KISS_CMD_FULLDUPLEX 0x05 +#define KISS_CMD_SETHARDWARE 0x06 +#define KISS_CMD_RETURN 0xFF + +#define KISS_DEFAULT_TXDELAY 50 +#define KISS_DEFAULT_PERSISTENCE 63 +#define KISS_DEFAULT_SLOTTIME 10 + +#define HW_CMD_GET_IDENTITY 0x01 +#define HW_CMD_GET_RANDOM 0x02 +#define HW_CMD_VERIFY_SIGNATURE 0x03 +#define HW_CMD_SIGN_DATA 0x04 +#define HW_CMD_ENCRYPT_DATA 0x05 +#define HW_CMD_DECRYPT_DATA 0x06 +#define HW_CMD_KEY_EXCHANGE 0x07 +#define HW_CMD_HASH 0x08 +#define HW_CMD_SET_RADIO 0x09 +#define HW_CMD_SET_TX_POWER 0x0A +#define HW_CMD_GET_RADIO 0x0C +#define HW_CMD_GET_TX_POWER 0x0D +#define HW_CMD_GET_VERSION 0x0F +#define HW_CMD_GET_CURRENT_RSSI 0x10 +#define HW_CMD_IS_CHANNEL_BUSY 0x11 +#define HW_CMD_GET_AIRTIME 0x12 +#define HW_CMD_GET_NOISE_FLOOR 0x13 +#define HW_CMD_GET_STATS 0x14 +#define HW_CMD_GET_BATTERY 0x15 +#define HW_CMD_PING 0x16 +#define HW_CMD_GET_SENSORS 0x17 + +#define HW_RESP_IDENTITY 0x21 +#define HW_RESP_RANDOM 0x22 +#define HW_RESP_VERIFY 0x23 +#define HW_RESP_SIGNATURE 0x24 +#define HW_RESP_ENCRYPTED 0x25 +#define HW_RESP_DECRYPTED 0x26 +#define HW_RESP_SHARED_SECRET 0x27 +#define HW_RESP_HASH 0x28 +#define HW_RESP_OK 0x29 +#define HW_RESP_RADIO 0x2A +#define HW_RESP_TX_POWER 0x2B +#define HW_RESP_VERSION 0x2D +#define HW_RESP_ERROR 0x2E +#define HW_RESP_TX_DONE 0x2F +#define HW_RESP_CURRENT_RSSI 0x30 +#define HW_RESP_CHANNEL_BUSY 0x31 +#define HW_RESP_AIRTIME 0x32 +#define HW_RESP_NOISE_FLOOR 0x33 +#define HW_RESP_STATS 0x34 +#define HW_RESP_BATTERY 0x35 +#define HW_RESP_PONG 0x36 +#define HW_RESP_SENSORS 0x37 +#define HW_RESP_RX_META 0x38 + +#define HW_ERR_INVALID_LENGTH 0x01 +#define HW_ERR_INVALID_PARAM 0x02 +#define HW_ERR_NO_CALLBACK 0x03 +#define HW_ERR_MAC_FAILED 0x04 +#define HW_ERR_UNKNOWN_CMD 0x05 +#define HW_ERR_ENCRYPT_FAILED 0x06 #define KISS_FIRMWARE_VERSION 1 @@ -74,6 +86,9 @@ typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); typedef float (*GetCurrentRssiCallback)(); typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); +typedef void (*SendPacketCallback)(const uint8_t* data, uint16_t len); +typedef bool (*IsSendCompleteCallback)(); +typedef void (*OnSendFinishedCallback)(); struct RadioConfig { uint32_t freq_hz; @@ -83,6 +98,14 @@ struct RadioConfig { uint8_t tx_power; }; +enum TxState { + TX_IDLE, + TX_WAIT_CLEAR, + TX_SLOT_WAIT, + TX_DELAY, + TX_SENDING +}; + class KissModem { Stream& _serial; mesh::LocalIdentity& _identity; @@ -90,28 +113,43 @@ class KissModem { mesh::Radio& _radio; mesh::MainBoard& _board; SensorManager& _sensors; - + uint8_t _rx_buf[KISS_MAX_FRAME_SIZE]; uint16_t _rx_len; bool _rx_escaped; bool _rx_active; - + uint8_t _pending_tx[KISS_MAX_PACKET_SIZE]; uint16_t _pending_tx_len; bool _has_pending_tx; + uint8_t _txdelay; + uint8_t _persistence; + uint8_t _slottime; + uint8_t _txtail; + uint8_t _fullduplex; + + TxState _tx_state; + uint32_t _tx_timer; + SetRadioCallback _setRadioCallback; SetTxPowerCallback _setTxPowerCallback; GetCurrentRssiCallback _getCurrentRssiCallback; GetStatsCallback _getStatsCallback; - + SendPacketCallback _sendPacketCallback; + IsSendCompleteCallback _isSendCompleteCallback; + OnSendFinishedCallback _onSendFinishedCallback; + RadioConfig _config; void writeByte(uint8_t b); - void writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len); - void writeErrorFrame(uint8_t error_code); + void writeFrame(uint8_t type, const uint8_t* data, uint16_t len); + void writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len); + void writeHardwareError(uint8_t error_code); void processFrame(); - + void handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint16_t len); + void processTx(); + void handleGetIdentity(); void handleGetRandom(const uint8_t* data, uint16_t len); void handleVerifySignature(const uint8_t* data, uint16_t len); @@ -137,16 +175,18 @@ class KissModem { public: KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors); - + void begin(); void loop(); - + void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } - - bool getPacketToSend(uint8_t* packet, uint16_t* len); + void setSendPacketCallback(SendPacketCallback cb) { _sendPacketCallback = cb; } + void setIsSendCompleteCallback(IsSendCompleteCallback cb) { _isSendCompleteCallback = cb; } + void setOnSendFinishedCallback(OnSendFinishedCallback cb) { _onSendFinishedCallback = cb; } + void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); - void onTxComplete(bool success); + bool isTxBusy() const { return _tx_state != TX_IDLE; } }; diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 3a610460d8..160adc6916 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -12,14 +12,9 @@ #include #endif -#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 -#define AGC_RESET_INTERVAL_MS 30000 - StdRNG rng; mesh::LocalIdentity identity; KissModem* modem; -static uint32_t next_noise_floor_calib_ms = 0; -static uint32_t next_agc_reset_ms = 0; void halt() { while (1) ; @@ -67,6 +62,18 @@ void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { *errors = radio_driver.getPacketsRecvErrors(); } +void onSendPacket(const uint8_t* data, uint16_t len) { + radio_driver.startSendRaw(data, len); +} + +bool onIsSendComplete() { + return radio_driver.isSendComplete(); +} + +void onSendFinished() { + radio_driver.onSendFinished(); +} + void setup() { board.begin(); @@ -91,40 +98,25 @@ void setup() { modem->setTxPowerCallback(onSetTxPower); modem->setGetCurrentRssiCallback(onGetCurrentRssi); modem->setGetStatsCallback(onGetStats); + modem->setSendPacketCallback(onSendPacket); + modem->setIsSendCompleteCallback(onIsSendComplete); + modem->setOnSendFinishedCallback(onSendFinished); modem->begin(); } void loop() { modem->loop(); - uint8_t packet[KISS_MAX_PACKET_SIZE]; - uint16_t len; + if (!modem->isTxBusy()) { + uint8_t rx_buf[256]; + int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); - // trigger noise floor calibration - if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { - radio_driver.triggerNoiseFloorCalibrate(0); - next_noise_floor_calib_ms = millis(); - } - radio_driver.loop(); - - if (modem->getPacketToSend(packet, &len)) { - radio_driver.startSendRaw(packet, len); - while (!radio_driver.isSendComplete()) { - delay(1); + if (rx_len > 0) { + int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); + int8_t rssi = (int8_t)radio_driver.getLastRSSI(); + modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } - radio_driver.onSendFinished(); - modem->onTxComplete(true); } - if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { - radio_driver.resetAGC(); - next_agc_reset_ms = millis(); - } - uint8_t rx_buf[256]; - int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); - if (rx_len > 0) { - int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); - int8_t rssi = (int8_t)radio_driver.getLastRSSI(); - modem->onPacketReceived(snr, rssi, rx_buf, rx_len); - } + radio_driver.loop(); } diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 6e96018bc3..fe2f546ee6 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -107,4 +107,13 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} - +<../examples/simple_room_server/*.cpp> \ No newline at end of file + +<../examples/simple_room_server/*.cpp> + +[env:Xiao_nrf52_kiss_modem] +extends = Xiao_nrf52 +build_flags = + ${Xiao_nrf52.build_flags} +build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/kiss_modem/*.cpp> +lib_deps = + ${Xiao_nrf52.lib_deps} \ No newline at end of file From f78617dbdb9d2c8332f59633509685eebd80f9a6 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Fri, 6 Feb 2026 11:13:28 +0100 Subject: [PATCH 327/409] Add periodic noise floor calibration and AGC reset --- examples/kiss_modem/main.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 160adc6916..62c1658f70 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -12,9 +12,14 @@ #include #endif +#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 +#define AGC_RESET_INTERVAL_MS 30000 + StdRNG rng; mesh::LocalIdentity identity; KissModem* modem; +static uint32_t next_noise_floor_calib_ms = 0; +static uint32_t next_agc_reset_ms = 0; void halt() { while (1) ; @@ -107,16 +112,24 @@ void setup() { void loop() { modem->loop(); + if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { + radio_driver.triggerNoiseFloorCalibrate(0); + next_noise_floor_calib_ms = millis(); + } + radio_driver.loop(); + if (!modem->isTxBusy()) { + if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { + radio_driver.resetAGC(); + next_agc_reset_ms = millis(); + } + uint8_t rx_buf[256]; int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); - if (rx_len > 0) { int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); int8_t rssi = (int8_t)radio_driver.getLastRSSI(); modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } } - - radio_driver.loop(); } From 203d86f87d7ccbd40997050e57af8815ae55917c Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Fri, 6 Feb 2026 11:19:16 +0100 Subject: [PATCH 328/409] Update documentation. --- docs/kiss_modem_protocol.md | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index e042053c63..9d8c31c654 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -168,6 +168,38 @@ All values little-endian. | SF | 1 byte | Spreading factor (5-12) | | CR | 1 byte | Coding rate (5-8) | +### Version (Version response) + +| Field | Size | Description | +|-------|------|-------------| +| Version | 1 byte | Firmware version | +| Reserved | 1 byte | Always 0 | + +### Encrypted (Encrypted response) + +| Field | Size | Description | +|-------|------|-------------| +| MAC | 2 bytes | HMAC-SHA256 truncated to 2 bytes | +| Ciphertext | variable | AES-128-CBC encrypted data | + +### Airtime (Airtime response) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Airtime | 4 bytes | uint32_t, estimated air time in milliseconds | + +### Noise Floor (NoiseFloor response) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Noise floor | 2 bytes | int16_t, dBm (signed) | + +The modem recalibrates the noise floor every 2 seconds with an AGC reset every 30 seconds. + ### Stats (Stats response) All values little-endian. @@ -178,6 +210,14 @@ All values little-endian. | TX | 4 bytes | Packets transmitted | | Errors | 4 bytes | Receive errors | +### Battery (Battery response) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Millivolts | 2 bytes | uint16_t, battery voltage in mV | + ### Sensor Permissions (GetSensors) | Bit | Value | Description | @@ -192,9 +232,19 @@ Use `0x07` for all permissions. Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) for parsing. +## Cryptographic Algorithms + +| Operation | Algorithm | +|-----------|-----------| +| Identity / Signing / Verification | Ed25519 | +| Key Exchange | X25519 (ECDH) | +| Encryption | AES-128-CBC + HMAC-SHA256 (MAC truncated to 2 bytes) | +| Hashing | SHA-256 | + ## Notes - Modem generates identity on first boot (stored in flash) +- All multi-byte values are little-endian unless stated otherwise - SNR values in RxMeta are multiplied by 4 for 0.25 dB precision - TxDone is sent as a SetHardware event after each transmission - Standard KISS clients receive only type 0x00 data frames and can safely ignore all SetHardware (0x06) frames From 02ddc05c30e781917863e26f68920839e9cf77bb Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Fri, 6 Feb 2026 11:43:59 +0100 Subject: [PATCH 329/409] Reorganise KISS protocol to close gaps. --- docs/kiss_modem_protocol.md | 79 ++++++++++++++++++++----------- examples/kiss_modem/KissModem.cpp | 31 ++++++++++++ examples/kiss_modem/KissModem.h | 58 +++++++++++++---------- 3 files changed, 116 insertions(+), 52 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 9d8c31c654..55e89129dc 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -96,17 +96,20 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The | Hash | `0x08` | Data to hash | | SetRadio | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | | SetTxPower | `0x0A` | Power dBm (1) | -| GetRadio | `0x0C` | - | -| GetTxPower | `0x0D` | - | -| GetVersion | `0x0F` | - | -| GetCurrentRssi | `0x10` | - | -| IsChannelBusy | `0x11` | - | -| GetAirtime | `0x12` | Packet length (1) | -| GetNoiseFloor | `0x13` | - | -| GetStats | `0x14` | - | -| GetBattery | `0x15` | - | -| Ping | `0x16` | - | -| GetSensors | `0x17` | Permissions (1) | +| GetRadio | `0x0B` | - | +| GetTxPower | `0x0C` | - | +| GetCurrentRssi | `0x0D` | - | +| IsChannelBusy | `0x0E` | - | +| GetAirtime | `0x0F` | Packet length (1) | +| GetNoiseFloor | `0x10` | - | +| GetVersion | `0x11` | - | +| GetStats | `0x12` | - | +| GetBattery | `0x13` | - | +| GetMCUTemp | `0x14` | - | +| GetSensors | `0x15` | Permissions (1) | +| GetDeviceName | `0x16` | - | +| Ping | `0x17` | - | +| Reboot | `0x18` | - | ### Response Sub-commands (TNC to Host) @@ -121,20 +124,22 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The | SharedSecret | `0x27` | Shared secret (32) | | Hash | `0x28` | SHA-256 hash (32) | | OK | `0x29` | - | -| Radio | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | -| TxPower | `0x2B` | Power dBm (1) | -| Version | `0x2D` | Version (1) + Reserved (1) | -| Error | `0x2E` | Error code (1) | -| TxDone | `0x2F` | Result (1): 0x00=failed, 0x01=success | -| CurrentRssi | `0x30` | RSSI dBm (1, signed) | -| ChannelBusy | `0x31` | Result (1): 0x00=clear, 0x01=busy | -| Airtime | `0x32` | Milliseconds (4) | -| NoiseFloor | `0x33` | dBm (2, signed) | -| Stats | `0x34` | RX (4) + TX (4) + Errors (4) | -| Battery | `0x35` | Millivolts (2) | -| Pong | `0x36` | - | -| Sensors | `0x37` | CayenneLPP payload | -| RxMeta | `0x38` | SNR (1) + RSSI (1) | +| Error | `0x2A` | Error code (1) | +| Radio | `0x2B` | Freq (4) + BW (4) + SF (1) + CR (1) | +| TxPower | `0x2C` | Power dBm (1) | +| CurrentRssi | `0x2D` | RSSI dBm (1, signed) | +| ChannelBusy | `0x2E` | Result (1): 0x00=clear, 0x01=busy | +| Airtime | `0x2F` | Milliseconds (4) | +| NoiseFloor | `0x30` | dBm (2, signed) | +| Version | `0x31` | Version (1) + Reserved (1) | +| Stats | `0x32` | RX (4) + TX (4) + Errors (4) | +| Battery | `0x33` | Millivolts (2) | +| MCUTemp | `0x34` | Temperature (2, signed) | +| Sensors | `0x35` | CayenneLPP payload | +| DeviceName | `0x36` | Name (variable, UTF-8) | +| Pong | `0x37` | - | +| TxDone | `0x38` | Result (1): 0x00=failed, 0x01=success | +| RxMeta | `0x39` | SNR (1) + RSSI (1) | ### Error Codes @@ -151,9 +156,9 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The The TNC sends these SetHardware frames without a preceding request: -**TxDone (0x2F)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. +**TxDone (0x38)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. -**RxMeta (0x38)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Standard KISS clients ignore this frame. +**RxMeta (0x39)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Standard KISS clients ignore this frame. ## Data Formats @@ -218,6 +223,26 @@ All values little-endian. |-------|------|-------------| | Millivolts | 2 bytes | uint16_t, battery voltage in mV | +### MCU Temperature (MCUTemp response) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Temperature | 2 bytes | int16_t, tenths of °C (e.g., 253 = 25.3°C) | + +Returns `NoCallback` error if the board does not support temperature readings. + +### Device Name (DeviceName response) + +| Field | Size | Description | +|-------|------|-------------| +| Name | variable | UTF-8 string, no null terminator | + +### Reboot + +Sends an `OK` response, flushes serial, then reboots the device. The host should expect the connection to drop. + ### Sensor Permissions (GetSensors) | Bit | Value | Description | diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 9915ec5ec2..31bb8a1e60 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -225,6 +225,15 @@ void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint case HW_CMD_GET_SENSORS: handleGetSensors(data, len); break; + case HW_CMD_GET_MCU_TEMP: + handleGetMCUTemp(); + break; + case HW_CMD_REBOOT: + handleReboot(); + break; + case HW_CMD_GET_DEVICE_NAME: + handleGetDeviceName(); + break; default: writeHardwareError(HW_ERR_UNKNOWN_CMD); break; @@ -536,3 +545,25 @@ void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { writeHardwareFrame(HW_RESP_SENSORS, nullptr, 0); } } + +void KissModem::handleGetMCUTemp() { + float temp = _board.getMCUTemperature(); + if (isnan(temp)) { + writeHardwareError(HW_ERR_NO_CALLBACK); + return; + } + int16_t temp_tenths = (int16_t)(temp * 10.0f); + writeHardwareFrame(HW_RESP_MCU_TEMP, (uint8_t*)&temp_tenths, 2); +} + +void KissModem::handleReboot() { + writeHardwareFrame(HW_RESP_OK, nullptr, 0); + _serial.flush(); + delay(50); + _board.reboot(); +} + +void KissModem::handleGetDeviceName() { + const char* name = _board.getManufacturerName(); + writeHardwareFrame(HW_RESP_DEVICE_NAME, (const uint8_t*)name, strlen(name)); +} diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 98f3c30031..6b56b91e0f 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -37,17 +37,20 @@ #define HW_CMD_HASH 0x08 #define HW_CMD_SET_RADIO 0x09 #define HW_CMD_SET_TX_POWER 0x0A -#define HW_CMD_GET_RADIO 0x0C -#define HW_CMD_GET_TX_POWER 0x0D -#define HW_CMD_GET_VERSION 0x0F -#define HW_CMD_GET_CURRENT_RSSI 0x10 -#define HW_CMD_IS_CHANNEL_BUSY 0x11 -#define HW_CMD_GET_AIRTIME 0x12 -#define HW_CMD_GET_NOISE_FLOOR 0x13 -#define HW_CMD_GET_STATS 0x14 -#define HW_CMD_GET_BATTERY 0x15 -#define HW_CMD_PING 0x16 -#define HW_CMD_GET_SENSORS 0x17 +#define HW_CMD_GET_RADIO 0x0B +#define HW_CMD_GET_TX_POWER 0x0C +#define HW_CMD_GET_CURRENT_RSSI 0x0D +#define HW_CMD_IS_CHANNEL_BUSY 0x0E +#define HW_CMD_GET_AIRTIME 0x0F +#define HW_CMD_GET_NOISE_FLOOR 0x10 +#define HW_CMD_GET_VERSION 0x11 +#define HW_CMD_GET_STATS 0x12 +#define HW_CMD_GET_BATTERY 0x13 +#define HW_CMD_GET_MCU_TEMP 0x14 +#define HW_CMD_GET_SENSORS 0x15 +#define HW_CMD_GET_DEVICE_NAME 0x16 +#define HW_CMD_PING 0x17 +#define HW_CMD_REBOOT 0x18 #define HW_RESP_IDENTITY 0x21 #define HW_RESP_RANDOM 0x22 @@ -58,20 +61,22 @@ #define HW_RESP_SHARED_SECRET 0x27 #define HW_RESP_HASH 0x28 #define HW_RESP_OK 0x29 -#define HW_RESP_RADIO 0x2A -#define HW_RESP_TX_POWER 0x2B -#define HW_RESP_VERSION 0x2D -#define HW_RESP_ERROR 0x2E -#define HW_RESP_TX_DONE 0x2F -#define HW_RESP_CURRENT_RSSI 0x30 -#define HW_RESP_CHANNEL_BUSY 0x31 -#define HW_RESP_AIRTIME 0x32 -#define HW_RESP_NOISE_FLOOR 0x33 -#define HW_RESP_STATS 0x34 -#define HW_RESP_BATTERY 0x35 -#define HW_RESP_PONG 0x36 -#define HW_RESP_SENSORS 0x37 -#define HW_RESP_RX_META 0x38 +#define HW_RESP_ERROR 0x2A +#define HW_RESP_RADIO 0x2B +#define HW_RESP_TX_POWER 0x2C +#define HW_RESP_CURRENT_RSSI 0x2D +#define HW_RESP_CHANNEL_BUSY 0x2E +#define HW_RESP_AIRTIME 0x2F +#define HW_RESP_NOISE_FLOOR 0x30 +#define HW_RESP_VERSION 0x31 +#define HW_RESP_STATS 0x32 +#define HW_RESP_BATTERY 0x33 +#define HW_RESP_MCU_TEMP 0x34 +#define HW_RESP_SENSORS 0x35 +#define HW_RESP_DEVICE_NAME 0x36 +#define HW_RESP_PONG 0x37 +#define HW_RESP_TX_DONE 0x38 +#define HW_RESP_RX_META 0x39 #define HW_ERR_INVALID_LENGTH 0x01 #define HW_ERR_INVALID_PARAM 0x02 @@ -171,6 +176,9 @@ class KissModem { void handleGetBattery(); void handlePing(); void handleGetSensors(const uint8_t* data, uint16_t len); + void handleGetMCUTemp(); + void handleReboot(); + void handleGetDeviceName(); public: KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, From 1af013c741f1461d3179162ce21ab2f6f6a26721 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Fri, 6 Feb 2026 12:36:13 +0100 Subject: [PATCH 330/409] Clarify data frame limitations in KISS modem documentation. --- docs/kiss_modem_protocol.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 55e89129dc..9652f976a5 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -56,7 +56,7 @@ Maximum unescaped frame size: 512 bytes. |------|-------|------|-------------| | Data | `0x00` | Raw packet | Received packet from radio | -Data frames carry raw packet data only, with no metadata prepended. +Data frames carry raw packet data only, with no metadata prepended. The Data command payload is limited to 255 bytes to match the MeshCore maximum transmission unit (MAX_TRANS_UNIT); frames larger than 255 bytes are silently dropped. The KISS specification recommends at least 1024 bytes for general-purpose TNCs; this modem is intended for MeshCore packets only, whose protocol MTU is 255 bytes. ### CSMA Behavior @@ -268,6 +268,7 @@ Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs. ## Notes +- Data payload limit (255 bytes) matches MeshCore MAX_TRANS_UNIT; no change needed for KISS “1024+ recommended” (that applies to general TNCs, not MeshCore) - Modem generates identity on first boot (stored in flash) - All multi-byte values are little-endian unless stated otherwise - SNR values in RxMeta are multiplied by 4 for 0.25 dB precision From f445b5acdc17ee6664cee77d5b113e2e54412eb2 Mon Sep 17 00:00:00 2001 From: agessaman Date: Fri, 6 Feb 2026 16:31:20 -0800 Subject: [PATCH 331/409] fix(kiss_modem): improve RX delivery and noise floor sampling --- examples/kiss_modem/KissModem.cpp | 5 +++++ examples/kiss_modem/KissModem.h | 2 ++ examples/kiss_modem/main.cpp | 32 +++++++++++++++++++++---------- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 31bb8a1e60..94e4fe8395 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -99,6 +99,11 @@ void KissModem::loop() { if (_rx_len < KISS_MAX_FRAME_SIZE) { _rx_buf[_rx_len++] = b; + } else { + /* Buffer full with no FEND; reset so we don't stay stuck ignoring input. */ + _rx_len = 0; + _rx_escaped = false; + _rx_active = false; } } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 6b56b91e0f..674b68f76c 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -197,4 +197,6 @@ class KissModem { void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); bool isTxBusy() const { return _tx_state != TX_IDLE; } + /** True only when radio is actually transmitting; use to skip recvRaw in main loop. */ + bool isActuallyTransmitting() const { return _tx_state == TX_SENDING; } }; diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 62c1658f70..adb201460d 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -112,16 +112,12 @@ void setup() { void loop() { modem->loop(); - if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { - radio_driver.triggerNoiseFloorCalibrate(0); - next_noise_floor_calib_ms = millis(); - } - radio_driver.loop(); - - if (!modem->isTxBusy()) { - if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { - radio_driver.resetAGC(); - next_agc_reset_ms = millis(); + if (!modem->isActuallyTransmitting()) { + if (!modem->isTxBusy()) { + if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { + radio_driver.resetAGC(); + next_agc_reset_ms = millis(); + } } uint8_t rx_buf[256]; @@ -131,5 +127,21 @@ void loop() { int8_t rssi = (int8_t)radio_driver.getLastRSSI(); modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } + /* Sample noise floor right after drain: we're in STATE_RX so wrapper can collect. */ + for (int i = 0; i < 16; i++) { + radio_driver.loop(); + } + } + + /* Trigger starts a new 64-sample calibration window every 2s. */ + if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { + radio_driver.triggerNoiseFloorCalibrate(0); + next_noise_floor_calib_ms = millis(); + } + radio_driver.loop(); + if (!modem->isActuallyTransmitting()) { + for (int i = 0; i < 15; i++) { + radio_driver.loop(); + } } } From 49e7516145a750f0c0d11f6cdf5ddcc8973fb6ae Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Fri, 6 Feb 2026 14:17:54 +0100 Subject: [PATCH 332/409] Add KISS UART support --- examples/kiss_modem/main.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index adb201460d..514897c3bc 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -11,6 +11,9 @@ #elif defined(ESP32) #include #endif +#if defined(KISS_UART_RX) && defined(KISS_UART_TX) + #include +#endif #define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 #define AGC_RESET_INTERVAL_MS 30000 @@ -91,14 +94,35 @@ void setup() { rng.begin(radio_get_rng_seed()); loadOrCreateIdentity(); + sensors.begin(); + +#if defined(KISS_UART_RX) && defined(KISS_UART_TX) +#if defined(ESP32) + Serial1.setPins(KISS_UART_RX, KISS_UART_TX); + Serial1.begin(115200); +#elif defined(NRF52_PLATFORM) + ((Uart *)&Serial1)->setPins(KISS_UART_RX, KISS_UART_TX); + Serial1.begin(115200); +#elif defined(RP2040_PLATFORM) + ((SerialUART *)&Serial1)->setRX(KISS_UART_RX); + ((SerialUART *)&Serial1)->setTX(KISS_UART_TX); + Serial1.begin(115200); +#elif defined(STM32_PLATFORM) + ((HardwareSerial *)&Serial1)->setRx(KISS_UART_RX); + ((HardwareSerial *)&Serial1)->setTx(KISS_UART_TX); + Serial1.begin(115200); +#else + #error "KISS UART not supported on this platform" +#endif + modem = new KissModem(Serial1, identity, rng, radio_driver, board, sensors); +#else Serial.begin(115200); uint32_t start = millis(); while (!Serial && millis() - start < 3000) delay(10); delay(100); - - sensors.begin(); - modem = new KissModem(Serial, identity, rng, radio_driver, board, sensors); +#endif + modem->setRadioCallback(onSetRadio); modem->setTxPowerCallback(onSetTxPower); modem->setGetCurrentRssiCallback(onGetCurrentRssi); From 7982d1ce1f0da6a97620a0fe4a223e1a77cf6dfc Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 7 Feb 2026 10:21:32 +0100 Subject: [PATCH 333/409] Use high-bit convention for hardware response codes --- examples/kiss_modem/KissModem.h | 57 ++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 674b68f76c..f364f3ce59 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -52,31 +52,38 @@ #define HW_CMD_PING 0x17 #define HW_CMD_REBOOT 0x18 -#define HW_RESP_IDENTITY 0x21 -#define HW_RESP_RANDOM 0x22 -#define HW_RESP_VERIFY 0x23 -#define HW_RESP_SIGNATURE 0x24 -#define HW_RESP_ENCRYPTED 0x25 -#define HW_RESP_DECRYPTED 0x26 -#define HW_RESP_SHARED_SECRET 0x27 -#define HW_RESP_HASH 0x28 -#define HW_RESP_OK 0x29 -#define HW_RESP_ERROR 0x2A -#define HW_RESP_RADIO 0x2B -#define HW_RESP_TX_POWER 0x2C -#define HW_RESP_CURRENT_RSSI 0x2D -#define HW_RESP_CHANNEL_BUSY 0x2E -#define HW_RESP_AIRTIME 0x2F -#define HW_RESP_NOISE_FLOOR 0x30 -#define HW_RESP_VERSION 0x31 -#define HW_RESP_STATS 0x32 -#define HW_RESP_BATTERY 0x33 -#define HW_RESP_MCU_TEMP 0x34 -#define HW_RESP_SENSORS 0x35 -#define HW_RESP_DEVICE_NAME 0x36 -#define HW_RESP_PONG 0x37 -#define HW_RESP_TX_DONE 0x38 -#define HW_RESP_RX_META 0x39 +/* Response code = command code | 0x80. Generic / unsolicited use 0xF0+. */ +#define HW_RESP(cmd) ((cmd) | 0x80) + +#define HW_RESP_IDENTITY HW_RESP(HW_CMD_GET_IDENTITY) /* 0x81 */ +#define HW_RESP_RANDOM HW_RESP(HW_CMD_GET_RANDOM) /* 0x82 */ +#define HW_RESP_VERIFY HW_RESP(HW_CMD_VERIFY_SIGNATURE) /* 0x83 */ +#define HW_RESP_SIGNATURE HW_RESP(HW_CMD_SIGN_DATA) /* 0x84 */ +#define HW_RESP_ENCRYPTED HW_RESP(HW_CMD_ENCRYPT_DATA) /* 0x85 */ +#define HW_RESP_DECRYPTED HW_RESP(HW_CMD_DECRYPT_DATA) /* 0x86 */ +#define HW_RESP_SHARED_SECRET HW_RESP(HW_CMD_KEY_EXCHANGE) /* 0x87 */ +#define HW_RESP_HASH HW_RESP(HW_CMD_HASH) /* 0x88 */ +#define HW_RESP_RADIO HW_RESP(HW_CMD_GET_RADIO) /* 0x8B */ +#define HW_RESP_TX_POWER HW_RESP(HW_CMD_GET_TX_POWER) /* 0x8C */ +#define HW_RESP_CURRENT_RSSI HW_RESP(HW_CMD_GET_CURRENT_RSSI) /* 0x8D */ +#define HW_RESP_CHANNEL_BUSY HW_RESP(HW_CMD_IS_CHANNEL_BUSY) /* 0x8E */ +#define HW_RESP_AIRTIME HW_RESP(HW_CMD_GET_AIRTIME) /* 0x8F */ +#define HW_RESP_NOISE_FLOOR HW_RESP(HW_CMD_GET_NOISE_FLOOR) /* 0x90 */ +#define HW_RESP_VERSION HW_RESP(HW_CMD_GET_VERSION) /* 0x91 */ +#define HW_RESP_STATS HW_RESP(HW_CMD_GET_STATS) /* 0x92 */ +#define HW_RESP_BATTERY HW_RESP(HW_CMD_GET_BATTERY) /* 0x93 */ +#define HW_RESP_MCU_TEMP HW_RESP(HW_CMD_GET_MCU_TEMP) /* 0x94 */ +#define HW_RESP_SENSORS HW_RESP(HW_CMD_GET_SENSORS) /* 0x95 */ +#define HW_RESP_DEVICE_NAME HW_RESP(HW_CMD_GET_DEVICE_NAME) /* 0x96 */ +#define HW_RESP_PONG HW_RESP(HW_CMD_PING) /* 0x97 */ + +/* Generic responses (shared by multiple commands) */ +#define HW_RESP_OK 0xF0 +#define HW_RESP_ERROR 0xF1 + +/* Unsolicited notifications (no corresponding request) */ +#define HW_RESP_TX_DONE 0xF8 +#define HW_RESP_RX_META 0xF9 #define HW_ERR_INVALID_LENGTH 0x01 #define HW_ERR_INVALID_PARAM 0x02 From 5ccd99e25f6926e515280c5bb1d154d305948d48 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 7 Feb 2026 10:21:36 +0100 Subject: [PATCH 334/409] Add toggleable per-packet signal reporting --- examples/kiss_modem/KissModem.cpp | 28 ++++++++++++++++++++++++++-- examples/kiss_modem/KissModem.h | 6 ++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 94e4fe8395..ebe2e98f6c 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -24,6 +24,7 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _isSendCompleteCallback = nullptr; _onSendFinishedCallback = nullptr; _config = {0, 0, 0, 0, 0}; + _signal_report_enabled = true; } void KissModem::begin() { @@ -239,6 +240,12 @@ void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint case HW_CMD_GET_DEVICE_NAME: handleGetDeviceName(); break; + case HW_CMD_SET_SIGNAL_REPORT: + handleSetSignalReport(data, len); + break; + case HW_CMD_GET_SIGNAL_REPORT: + handleGetSignalReport(); + break; default: writeHardwareError(HW_ERR_UNKNOWN_CMD); break; @@ -304,8 +311,10 @@ void KissModem::processTx() { void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { writeFrame(KISS_CMD_DATA, packet, len); - uint8_t meta[2] = { (uint8_t)snr, (uint8_t)rssi }; - writeHardwareFrame(HW_RESP_RX_META, meta, 2); + if (_signal_report_enabled) { + uint8_t meta[2] = { (uint8_t)snr, (uint8_t)rssi }; + writeHardwareFrame(HW_RESP_RX_META, meta, 2); + } } void KissModem::handleGetIdentity() { @@ -572,3 +581,18 @@ void KissModem::handleGetDeviceName() { const char* name = _board.getManufacturerName(); writeHardwareFrame(HW_RESP_DEVICE_NAME, (const uint8_t*)name, strlen(name)); } + +void KissModem::handleSetSignalReport(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeHardwareError(HW_ERR_INVALID_LENGTH); + return; + } + _signal_report_enabled = (data[0] != 0x00); + uint8_t val = _signal_report_enabled ? 0x01 : 0x00; + writeHardwareFrame(HW_RESP_SIGNAL_REPORT, &val, 1); +} + +void KissModem::handleGetSignalReport() { + uint8_t val = _signal_report_enabled ? 0x01 : 0x00; + writeHardwareFrame(HW_RESP_SIGNAL_REPORT, &val, 1); +} diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index f364f3ce59..6a5e25dd0d 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -51,6 +51,8 @@ #define HW_CMD_GET_DEVICE_NAME 0x16 #define HW_CMD_PING 0x17 #define HW_CMD_REBOOT 0x18 +#define HW_CMD_SET_SIGNAL_REPORT 0x19 +#define HW_CMD_GET_SIGNAL_REPORT 0x1A /* Response code = command code | 0x80. Generic / unsolicited use 0xF0+. */ #define HW_RESP(cmd) ((cmd) | 0x80) @@ -76,6 +78,7 @@ #define HW_RESP_SENSORS HW_RESP(HW_CMD_GET_SENSORS) /* 0x95 */ #define HW_RESP_DEVICE_NAME HW_RESP(HW_CMD_GET_DEVICE_NAME) /* 0x96 */ #define HW_RESP_PONG HW_RESP(HW_CMD_PING) /* 0x97 */ +#define HW_RESP_SIGNAL_REPORT HW_RESP(HW_CMD_GET_SIGNAL_REPORT) /* 0x9A */ /* Generic responses (shared by multiple commands) */ #define HW_RESP_OK 0xF0 @@ -153,6 +156,7 @@ class KissModem { OnSendFinishedCallback _onSendFinishedCallback; RadioConfig _config; + bool _signal_report_enabled; void writeByte(uint8_t b); void writeFrame(uint8_t type, const uint8_t* data, uint16_t len); @@ -186,6 +190,8 @@ class KissModem { void handleGetMCUTemp(); void handleReboot(); void handleGetDeviceName(); + void handleSetSignalReport(const uint8_t* data, uint16_t len); + void handleGetSignalReport(); public: KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, From 362b5eb0a100a8e295fe1085df88d508466c4e37 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 7 Feb 2026 10:26:08 +0100 Subject: [PATCH 335/409] Update protocol docs for new response codes and signal reporting --- docs/kiss_modem_protocol.md | 59 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 9652f976a5..6a08614fa1 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -110,36 +110,41 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The | GetDeviceName | `0x16` | - | | Ping | `0x17` | - | | Reboot | `0x18` | - | +| SetSignalReport | `0x19` | Enable (1): 0x00=disable, nonzero=enable | +| GetSignalReport | `0x1A` | - | ### Response Sub-commands (TNC to Host) +Response codes use the high-bit convention: `response = command | 0x80`. Generic and unsolicited responses use the `0xF0`+ range. + | Sub-command | Value | Data | |-------------|-------|------| -| Identity | `0x21` | PubKey (32) | -| Random | `0x22` | Random bytes (1-64) | -| Verify | `0x23` | Result (1): 0x00=invalid, 0x01=valid | -| Signature | `0x24` | Signature (64) | -| Encrypted | `0x25` | MAC (2) + Ciphertext | -| Decrypted | `0x26` | Plaintext | -| SharedSecret | `0x27` | Shared secret (32) | -| Hash | `0x28` | SHA-256 hash (32) | -| OK | `0x29` | - | -| Error | `0x2A` | Error code (1) | -| Radio | `0x2B` | Freq (4) + BW (4) + SF (1) + CR (1) | -| TxPower | `0x2C` | Power dBm (1) | -| CurrentRssi | `0x2D` | RSSI dBm (1, signed) | -| ChannelBusy | `0x2E` | Result (1): 0x00=clear, 0x01=busy | -| Airtime | `0x2F` | Milliseconds (4) | -| NoiseFloor | `0x30` | dBm (2, signed) | -| Version | `0x31` | Version (1) + Reserved (1) | -| Stats | `0x32` | RX (4) + TX (4) + Errors (4) | -| Battery | `0x33` | Millivolts (2) | -| MCUTemp | `0x34` | Temperature (2, signed) | -| Sensors | `0x35` | CayenneLPP payload | -| DeviceName | `0x36` | Name (variable, UTF-8) | -| Pong | `0x37` | - | -| TxDone | `0x38` | Result (1): 0x00=failed, 0x01=success | -| RxMeta | `0x39` | SNR (1) + RSSI (1) | +| Identity | `0x81` | PubKey (32) | +| Random | `0x82` | Random bytes (1-64) | +| Verify | `0x83` | Result (1): 0x00=invalid, 0x01=valid | +| Signature | `0x84` | Signature (64) | +| Encrypted | `0x85` | MAC (2) + Ciphertext | +| Decrypted | `0x86` | Plaintext | +| SharedSecret | `0x87` | Shared secret (32) | +| Hash | `0x88` | SHA-256 hash (32) | +| Radio | `0x8B` | Freq (4) + BW (4) + SF (1) + CR (1) | +| TxPower | `0x8C` | Power dBm (1) | +| CurrentRssi | `0x8D` | RSSI dBm (1, signed) | +| ChannelBusy | `0x8E` | Result (1): 0x00=clear, 0x01=busy | +| Airtime | `0x8F` | Milliseconds (4) | +| NoiseFloor | `0x90` | dBm (2, signed) | +| Version | `0x91` | Version (1) + Reserved (1) | +| Stats | `0x92` | RX (4) + TX (4) + Errors (4) | +| Battery | `0x93` | Millivolts (2) | +| MCUTemp | `0x94` | Temperature (2, signed) | +| Sensors | `0x95` | CayenneLPP payload | +| DeviceName | `0x96` | Name (variable, UTF-8) | +| Pong | `0x97` | - | +| SignalReport | `0x9A` | Status (1): 0x00=disabled, 0x01=enabled | +| OK | `0xF0` | - | +| Error | `0xF1` | Error code (1) | +| TxDone | `0xF8` | Result (1): 0x00=failed, 0x01=success | +| RxMeta | `0xF9` | SNR (1) + RSSI (1) | ### Error Codes @@ -156,9 +161,9 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The The TNC sends these SetHardware frames without a preceding request: -**TxDone (0x38)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. +**TxDone (0xF8)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. -**RxMeta (0x39)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Standard KISS clients ignore this frame. +**RxMeta (0xF9)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Enabled by default; can be toggled with SetSignalReport. Standard KISS clients ignore this frame. ## Data Formats From 00b44c41148e00e73b0289a78662987b44ae4809 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 7 Feb 2026 14:22:21 +0100 Subject: [PATCH 336/409] Remove redundant send/complete/finished callbacks, use Radio interface directly --- examples/kiss_modem/KissModem.cpp | 16 ++++------------ examples/kiss_modem/KissModem.h | 9 --------- examples/kiss_modem/main.cpp | 15 --------------- 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index ebe2e98f6c..08ed9b906d 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -20,9 +20,6 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _setTxPowerCallback = nullptr; _getCurrentRssiCallback = nullptr; _getStatsCallback = nullptr; - _sendPacketCallback = nullptr; - _isSendCompleteCallback = nullptr; - _onSendFinishedCallback = nullptr; _config = {0, 0, 0, 0, 0}; _signal_report_enabled = true; } @@ -287,19 +284,14 @@ void KissModem::processTx() { case TX_DELAY: if (millis() - _tx_timer >= (uint32_t)_txdelay * 10) { - if (_sendPacketCallback) { - _sendPacketCallback(_pending_tx, _pending_tx_len); - _tx_state = TX_SENDING; - } else { - _has_pending_tx = false; - _tx_state = TX_IDLE; - } + _radio.startSendRaw(_pending_tx, _pending_tx_len); + _tx_state = TX_SENDING; } break; case TX_SENDING: - if (_isSendCompleteCallback && _isSendCompleteCallback()) { - if (_onSendFinishedCallback) _onSendFinishedCallback(); + if (_radio.isSendComplete()) { + _radio.onSendFinished(); uint8_t result = 0x01; writeHardwareFrame(HW_RESP_TX_DONE, &result, 1); _has_pending_tx = false; diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 6a5e25dd0d..88741e1f8f 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -101,9 +101,6 @@ typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); typedef float (*GetCurrentRssiCallback)(); typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); -typedef void (*SendPacketCallback)(const uint8_t* data, uint16_t len); -typedef bool (*IsSendCompleteCallback)(); -typedef void (*OnSendFinishedCallback)(); struct RadioConfig { uint32_t freq_hz; @@ -151,9 +148,6 @@ class KissModem { SetTxPowerCallback _setTxPowerCallback; GetCurrentRssiCallback _getCurrentRssiCallback; GetStatsCallback _getStatsCallback; - SendPacketCallback _sendPacketCallback; - IsSendCompleteCallback _isSendCompleteCallback; - OnSendFinishedCallback _onSendFinishedCallback; RadioConfig _config; bool _signal_report_enabled; @@ -204,9 +198,6 @@ class KissModem { void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } - void setSendPacketCallback(SendPacketCallback cb) { _sendPacketCallback = cb; } - void setIsSendCompleteCallback(IsSendCompleteCallback cb) { _isSendCompleteCallback = cb; } - void setOnSendFinishedCallback(OnSendFinishedCallback cb) { _onSendFinishedCallback = cb; } void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); bool isTxBusy() const { return _tx_state != TX_IDLE; } diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 514897c3bc..15888b9056 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -70,18 +70,6 @@ void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { *errors = radio_driver.getPacketsRecvErrors(); } -void onSendPacket(const uint8_t* data, uint16_t len) { - radio_driver.startSendRaw(data, len); -} - -bool onIsSendComplete() { - return radio_driver.isSendComplete(); -} - -void onSendFinished() { - radio_driver.onSendFinished(); -} - void setup() { board.begin(); @@ -127,9 +115,6 @@ void setup() { modem->setTxPowerCallback(onSetTxPower); modem->setGetCurrentRssiCallback(onGetCurrentRssi); modem->setGetStatsCallback(onGetStats); - modem->setSendPacketCallback(onSendPacket); - modem->setIsSendCompleteCallback(onIsSendComplete); - modem->setOnSendFinishedCallback(onSendFinished); modem->begin(); } From 5157daf1c1ae341a36d1efa21e307aef6c682321 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 7 Feb 2026 14:24:34 +0100 Subject: [PATCH 337/409] Remove individual HW_RESP_* defines, use HW_RESP() macro directly --- examples/kiss_modem/KissModem.cpp | 48 +++++++++++++++---------------- examples/kiss_modem/KissModem.h | 23 --------------- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 08ed9b906d..b4251046bc 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -310,7 +310,7 @@ void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, } void KissModem::handleGetIdentity() { - writeHardwareFrame(HW_RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); + writeHardwareFrame(HW_RESP(HW_CMD_GET_IDENTITY), _identity.pub_key, PUB_KEY_SIZE); } void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { @@ -327,7 +327,7 @@ void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { uint8_t buf[64]; _rng.random(buf, requested); - writeHardwareFrame(HW_RESP_RANDOM, buf, requested); + writeHardwareFrame(HW_RESP(HW_CMD_GET_RANDOM), buf, requested); } void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { @@ -342,7 +342,7 @@ void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE; uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00; - writeHardwareFrame(HW_RESP_VERIFY, &result, 1); + writeHardwareFrame(HW_RESP(HW_CMD_VERIFY_SIGNATURE), &result, 1); } void KissModem::handleSignData(const uint8_t* data, uint16_t len) { @@ -353,7 +353,7 @@ void KissModem::handleSignData(const uint8_t* data, uint16_t len) { uint8_t signature[SIGNATURE_SIZE]; _identity.sign(signature, data, len); - writeHardwareFrame(HW_RESP_SIGNATURE, signature, SIGNATURE_SIZE); + writeHardwareFrame(HW_RESP(HW_CMD_SIGN_DATA), signature, SIGNATURE_SIZE); } void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { @@ -370,7 +370,7 @@ void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len); if (encrypted_len > 0) { - writeHardwareFrame(HW_RESP_ENCRYPTED, buf, encrypted_len); + writeHardwareFrame(HW_RESP(HW_CMD_ENCRYPT_DATA), buf, encrypted_len); } else { writeHardwareError(HW_ERR_ENCRYPT_FAILED); } @@ -390,7 +390,7 @@ void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) { int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len); if (decrypted_len > 0) { - writeHardwareFrame(HW_RESP_DECRYPTED, buf, decrypted_len); + writeHardwareFrame(HW_RESP(HW_CMD_DECRYPT_DATA), buf, decrypted_len); } else { writeHardwareError(HW_ERR_MAC_FAILED); } @@ -404,7 +404,7 @@ void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) { uint8_t shared_secret[PUB_KEY_SIZE]; _identity.calcSharedSecret(shared_secret, data); - writeHardwareFrame(HW_RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); + writeHardwareFrame(HW_RESP(HW_CMD_KEY_EXCHANGE), shared_secret, PUB_KEY_SIZE); } void KissModem::handleHash(const uint8_t* data, uint16_t len) { @@ -415,7 +415,7 @@ void KissModem::handleHash(const uint8_t* data, uint16_t len) { uint8_t hash[32]; mesh::Utils::sha256(hash, 32, data, len); - writeHardwareFrame(HW_RESP_HASH, hash, 32); + writeHardwareFrame(HW_RESP(HW_CMD_HASH), hash, 32); } void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { @@ -467,18 +467,18 @@ void KissModem::handleGetRadio() { memcpy(buf + 4, &_config.bw_hz, 4); buf[8] = _config.sf; buf[9] = _config.cr; - writeHardwareFrame(HW_RESP_RADIO, buf, 10); + writeHardwareFrame(HW_RESP(HW_CMD_GET_RADIO), buf, 10); } void KissModem::handleGetTxPower() { - writeHardwareFrame(HW_RESP_TX_POWER, &_config.tx_power, 1); + writeHardwareFrame(HW_RESP(HW_CMD_GET_TX_POWER), &_config.tx_power, 1); } void KissModem::handleGetVersion() { uint8_t buf[2]; buf[0] = KISS_FIRMWARE_VERSION; buf[1] = 0; - writeHardwareFrame(HW_RESP_VERSION, buf, 2); + writeHardwareFrame(HW_RESP(HW_CMD_GET_VERSION), buf, 2); } void KissModem::handleGetCurrentRssi() { @@ -489,12 +489,12 @@ void KissModem::handleGetCurrentRssi() { float rssi = _getCurrentRssiCallback(); int8_t rssi_byte = (int8_t)rssi; - writeHardwareFrame(HW_RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); + writeHardwareFrame(HW_RESP(HW_CMD_GET_CURRENT_RSSI), (uint8_t*)&rssi_byte, 1); } void KissModem::handleIsChannelBusy() { uint8_t busy = _radio.isReceiving() ? 0x01 : 0x00; - writeHardwareFrame(HW_RESP_CHANNEL_BUSY, &busy, 1); + writeHardwareFrame(HW_RESP(HW_CMD_IS_CHANNEL_BUSY), &busy, 1); } void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { @@ -505,12 +505,12 @@ void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { uint8_t packet_len = data[0]; uint32_t airtime = _radio.getEstAirtimeFor(packet_len); - writeHardwareFrame(HW_RESP_AIRTIME, (uint8_t*)&airtime, 4); + writeHardwareFrame(HW_RESP(HW_CMD_GET_AIRTIME), (uint8_t*)&airtime, 4); } void KissModem::handleGetNoiseFloor() { int16_t noise_floor = _radio.getNoiseFloor(); - writeHardwareFrame(HW_RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); + writeHardwareFrame(HW_RESP(HW_CMD_GET_NOISE_FLOOR), (uint8_t*)&noise_floor, 2); } void KissModem::handleGetStats() { @@ -525,16 +525,16 @@ void KissModem::handleGetStats() { memcpy(buf, &rx, 4); memcpy(buf + 4, &tx, 4); memcpy(buf + 8, &errors, 4); - writeHardwareFrame(HW_RESP_STATS, buf, 12); + writeHardwareFrame(HW_RESP(HW_CMD_GET_STATS), buf, 12); } void KissModem::handleGetBattery() { uint16_t mv = _board.getBattMilliVolts(); - writeHardwareFrame(HW_RESP_BATTERY, (uint8_t*)&mv, 2); + writeHardwareFrame(HW_RESP(HW_CMD_GET_BATTERY), (uint8_t*)&mv, 2); } void KissModem::handlePing() { - writeHardwareFrame(HW_RESP_PONG, nullptr, 0); + writeHardwareFrame(HW_RESP(HW_CMD_PING), nullptr, 0); } void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { @@ -546,9 +546,9 @@ void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { uint8_t permissions = data[0]; CayenneLPP telemetry(255); if (_sensors.querySensors(permissions, telemetry)) { - writeHardwareFrame(HW_RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); + writeHardwareFrame(HW_RESP(HW_CMD_GET_SENSORS), telemetry.getBuffer(), telemetry.getSize()); } else { - writeHardwareFrame(HW_RESP_SENSORS, nullptr, 0); + writeHardwareFrame(HW_RESP(HW_CMD_GET_SENSORS), nullptr, 0); } } @@ -559,7 +559,7 @@ void KissModem::handleGetMCUTemp() { return; } int16_t temp_tenths = (int16_t)(temp * 10.0f); - writeHardwareFrame(HW_RESP_MCU_TEMP, (uint8_t*)&temp_tenths, 2); + writeHardwareFrame(HW_RESP(HW_CMD_GET_MCU_TEMP), (uint8_t*)&temp_tenths, 2); } void KissModem::handleReboot() { @@ -571,7 +571,7 @@ void KissModem::handleReboot() { void KissModem::handleGetDeviceName() { const char* name = _board.getManufacturerName(); - writeHardwareFrame(HW_RESP_DEVICE_NAME, (const uint8_t*)name, strlen(name)); + writeHardwareFrame(HW_RESP(HW_CMD_GET_DEVICE_NAME), (const uint8_t*)name, strlen(name)); } void KissModem::handleSetSignalReport(const uint8_t* data, uint16_t len) { @@ -581,10 +581,10 @@ void KissModem::handleSetSignalReport(const uint8_t* data, uint16_t len) { } _signal_report_enabled = (data[0] != 0x00); uint8_t val = _signal_report_enabled ? 0x01 : 0x00; - writeHardwareFrame(HW_RESP_SIGNAL_REPORT, &val, 1); + writeHardwareFrame(HW_RESP(HW_CMD_GET_SIGNAL_REPORT), &val, 1); } void KissModem::handleGetSignalReport() { uint8_t val = _signal_report_enabled ? 0x01 : 0x00; - writeHardwareFrame(HW_RESP_SIGNAL_REPORT, &val, 1); + writeHardwareFrame(HW_RESP(HW_CMD_GET_SIGNAL_REPORT), &val, 1); } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 88741e1f8f..60566add92 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -57,29 +57,6 @@ /* Response code = command code | 0x80. Generic / unsolicited use 0xF0+. */ #define HW_RESP(cmd) ((cmd) | 0x80) -#define HW_RESP_IDENTITY HW_RESP(HW_CMD_GET_IDENTITY) /* 0x81 */ -#define HW_RESP_RANDOM HW_RESP(HW_CMD_GET_RANDOM) /* 0x82 */ -#define HW_RESP_VERIFY HW_RESP(HW_CMD_VERIFY_SIGNATURE) /* 0x83 */ -#define HW_RESP_SIGNATURE HW_RESP(HW_CMD_SIGN_DATA) /* 0x84 */ -#define HW_RESP_ENCRYPTED HW_RESP(HW_CMD_ENCRYPT_DATA) /* 0x85 */ -#define HW_RESP_DECRYPTED HW_RESP(HW_CMD_DECRYPT_DATA) /* 0x86 */ -#define HW_RESP_SHARED_SECRET HW_RESP(HW_CMD_KEY_EXCHANGE) /* 0x87 */ -#define HW_RESP_HASH HW_RESP(HW_CMD_HASH) /* 0x88 */ -#define HW_RESP_RADIO HW_RESP(HW_CMD_GET_RADIO) /* 0x8B */ -#define HW_RESP_TX_POWER HW_RESP(HW_CMD_GET_TX_POWER) /* 0x8C */ -#define HW_RESP_CURRENT_RSSI HW_RESP(HW_CMD_GET_CURRENT_RSSI) /* 0x8D */ -#define HW_RESP_CHANNEL_BUSY HW_RESP(HW_CMD_IS_CHANNEL_BUSY) /* 0x8E */ -#define HW_RESP_AIRTIME HW_RESP(HW_CMD_GET_AIRTIME) /* 0x8F */ -#define HW_RESP_NOISE_FLOOR HW_RESP(HW_CMD_GET_NOISE_FLOOR) /* 0x90 */ -#define HW_RESP_VERSION HW_RESP(HW_CMD_GET_VERSION) /* 0x91 */ -#define HW_RESP_STATS HW_RESP(HW_CMD_GET_STATS) /* 0x92 */ -#define HW_RESP_BATTERY HW_RESP(HW_CMD_GET_BATTERY) /* 0x93 */ -#define HW_RESP_MCU_TEMP HW_RESP(HW_CMD_GET_MCU_TEMP) /* 0x94 */ -#define HW_RESP_SENSORS HW_RESP(HW_CMD_GET_SENSORS) /* 0x95 */ -#define HW_RESP_DEVICE_NAME HW_RESP(HW_CMD_GET_DEVICE_NAME) /* 0x96 */ -#define HW_RESP_PONG HW_RESP(HW_CMD_PING) /* 0x97 */ -#define HW_RESP_SIGNAL_REPORT HW_RESP(HW_CMD_GET_SIGNAL_REPORT) /* 0x9A */ - /* Generic responses (shared by multiple commands) */ #define HW_RESP_OK 0xF0 #define HW_RESP_ERROR 0xF1 From f6ebbd978e0b27f5db15f2013cb4b5e0410a8edd Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 7 Feb 2026 14:32:11 +0100 Subject: [PATCH 338/409] Remove redundant locals in handleSetRadio --- examples/kiss_modem/KissModem.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index b4251046bc..5e8b00d521 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -428,21 +428,12 @@ void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { return; } - uint32_t freq_hz, bw_hz; - memcpy(&freq_hz, data, 4); - memcpy(&bw_hz, data + 4, 4); - uint8_t sf = data[8]; - uint8_t cr = data[9]; + memcpy(&_config.freq_hz, data, 4); + memcpy(&_config.bw_hz, data + 4, 4); + _config.sf = data[8]; + _config.cr = data[9]; - _config.freq_hz = freq_hz; - _config.bw_hz = bw_hz; - _config.sf = sf; - _config.cr = cr; - - float freq = freq_hz / 1000000.0f; - float bw = bw_hz / 1000.0f; - - _setRadioCallback(freq, bw, sf, cr); + _setRadioCallback(_config.freq_hz / 1000000.0f, _config.bw_hz / 1000.0f, _config.sf, _config.cr); writeHardwareFrame(HW_RESP_OK, nullptr, 0); } From c4c287d01bd67dbc65028f21ca0892b89228bf21 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 7 Feb 2026 15:39:24 +0100 Subject: [PATCH 339/409] Bridge always has work (prevents sleep) --- examples/simple_repeater/MyMesh.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6d957cc094..40b54555ab 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1219,5 +1219,8 @@ void MyMesh::loop() { // To check if there is pending work bool MyMesh::hasPendingWork() const { +#if defined(WITH_BRIDGE) + if (bridge.isRunning()) return true; // bridge needs WiFi radio, can't sleep +#endif return _mgr->getOutboundCount(0xFFFFFFFF) > 0; } From 23b4baa0665cdd762b1a909d1f1ce9f0a22d4fd8 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 7 Feb 2026 16:04:01 +0100 Subject: [PATCH 340/409] Enable register patch heltec tracker v2 --- variants/heltec_tracker_v2/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 36de671e25..25d16f2f66 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -26,6 +26,7 @@ build_flags = -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_REGISTER_PATCH=1 -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=0 From 776131e263d102675111dd6f51e2bf6be3f8b7cb Mon Sep 17 00:00:00 2001 From: agessaman Date: Sat, 7 Feb 2026 07:42:52 -0800 Subject: [PATCH 341/409] simplify kiss noise floor sampling --- examples/kiss_modem/main.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 15888b9056..3507959297 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -136,21 +136,11 @@ void loop() { int8_t rssi = (int8_t)radio_driver.getLastRSSI(); modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } - /* Sample noise floor right after drain: we're in STATE_RX so wrapper can collect. */ - for (int i = 0; i < 16; i++) { - radio_driver.loop(); - } } - /* Trigger starts a new 64-sample calibration window every 2s. */ if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { radio_driver.triggerNoiseFloorCalibrate(0); next_noise_floor_calib_ms = millis(); } radio_driver.loop(); - if (!modem->isActuallyTransmitting()) { - for (int i = 0; i < 15; i++) { - radio_driver.loop(); - } - } } From e8646f5ede777bc883079eec42d6989aa14b8f7a Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 7 Feb 2026 16:58:06 +0100 Subject: [PATCH 342/409] Parse as signed int --- examples/companion_radio/MyMesh.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index f8e90be5e0..9671609156 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1228,10 +1228,11 @@ void MyMesh::handleCmdFrame(size_t len) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } } else if (cmd_frame[0] == CMD_SET_RADIO_TX_POWER) { - if (cmd_frame[1] > MAX_LORA_TX_POWER) { + int8_t power = (int8_t)cmd_frame[1]; + if (power < -9 || power > MAX_LORA_TX_POWER) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else { - _prefs.tx_power_dbm = cmd_frame[1]; + _prefs.tx_power_dbm = power; savePrefs(); radio_set_tx_power(_prefs.tx_power_dbm); writeOKFrame(); From fcfbb458f82a5ca5f49a8225ebede2f84ec7a240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sat, 7 Feb 2026 21:26:28 +0000 Subject: [PATCH 343/409] Refactor environment names and build flags for RAK variants --- variants/rak11310/platformio.ini | 15 +++++++------ variants/rak3112/platformio.ini | 37 ++++++++------------------------ variants/rak3401/platformio.ini | 1 - variants/rak3x72/platformio.ini | 7 +++--- 4 files changed, 21 insertions(+), 39 deletions(-) diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini index df99ea8433..950b46efa2 100644 --- a/variants/rak11310/platformio.ini +++ b/variants/rak11310/platformio.ini @@ -7,6 +7,7 @@ board = rakwireless_rak11300 board_build.filesystem_size = 0.5m build_flags = ${rp2040_base.build_flags} -I variants/rak11310 + -D RAK_11310 -D ARDUINO_RAKWIRELESS_RAK11300=1 -D SX126X_CURRENT_LIMIT=140 -D RADIO_CLASS=CustomSX1262 @@ -34,7 +35,7 @@ build_src_filter = ${rp2040_base.build_src_filter} +<../variants/rak11310> lib_deps = ${rp2040_base.lib_deps} -[env:rak11310_repeater] +[env:RAK_11310_repeater] extends = rak11310 build_flags = ${rak11310.build_flags} -D ADVERT_NAME='"RAK11310 Repeater"' @@ -47,7 +48,7 @@ build_flags = ${rak11310.build_flags} build_src_filter = ${rak11310.build_src_filter} +<../examples/simple_repeater> -[env:rak11310_repeater_bridge_rs232] +[env:RAK_11310_repeater_bridge_rs232] extends = rak11310 build_flags = ${rak11310.build_flags} -D ADVERT_NAME='"RS232 Bridge"' @@ -65,7 +66,7 @@ build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_repeater> -[env:rak11310_room_server] +[env:RAK_11310_room_server] extends = rak11310 build_flags = ${rak11310.build_flags} -D ADVERT_NAME='"RAK11310 Room"' @@ -78,7 +79,7 @@ build_flags = ${rak11310.build_flags} build_src_filter = ${rak11310.build_src_filter} +<../examples/simple_room_server> -[env:rak11310_companion_radio_usb] +[env:RAK_11310_companion_radio_usb] extends = rak11310 build_flags = ${rak11310.build_flags} -D MAX_CONTACTS=100 @@ -90,7 +91,7 @@ build_src_filter = ${rak11310.build_src_filter} lib_deps = ${rak11310.lib_deps} densaugeo/base64 @ ~1.4.0 -; [env:rak11310_companion_radio_ble] +; [env:RAK_11310_companion_radio_ble] ; extends = rak11310 ; build_flags = ${rak11310.build_flags} ; -D MAX_CONTACTS=100 @@ -104,7 +105,7 @@ lib_deps = ${rak11310.lib_deps} ; lib_deps = ${rak11310.lib_deps} ; densaugeo/base64 @ ~1.4.0 -; [env:rak11310_companion_radio_wifi] +; [env:RAK_11310_companion_radio_wifi] ; extends = rak11310 ; build_flags = ${rak11310.build_flags} ; -D MAX_CONTACTS=100 @@ -119,7 +120,7 @@ lib_deps = ${rak11310.lib_deps} ; lib_deps = ${rak11310.lib_deps} ; densaugeo/base64 @ ~1.4.0 -[env:rak11310_terminal_chat] +[env:RAK_11310_terminal_chat] extends = rak11310 build_flags = ${rak11310.build_flags} -D MAX_CONTACTS=100 diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index 29ebdff27c..bc43ee04dc 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -36,11 +36,10 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[env:RAK3112_repeater] +[env:RAK_3112_repeater] extends = rak3112 build_flags = ${rak3112.build_flags} - -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"RAK3112 Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -49,18 +48,16 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - + +<../examples/simple_repeater> lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 -[env:RAK3112_repeater_bridge_rs232] +[env:RAK_3112_repeater_bridge_rs232] extends = rak3112 build_flags = ${rak3112.build_flags} - -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"RS232 Bridge"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -74,17 +71,15 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} + - + +<../examples/simple_repeater> lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} -[env:RAK3112_repeater_bridge_espnow] +[env:RAK_3112_repeater_bridge_espnow] extends = rak3112 build_flags = ${rak3112.build_flags} - -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -96,17 +91,15 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} + - + +<../examples/simple_repeater> lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} -[env:RAK3112_room_server] +[env:RAK_3112_room_server] extends = rak3112 build_flags = ${rak3112.build_flags} - -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"RAK3112 Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -115,13 +108,12 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - + +<../examples/simple_room_server> lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} -[env:RAK3112_terminal_chat] +[env:RAK_3112_terminal_chat] extends = rak3112 build_flags = ${rak3112.build_flags} @@ -135,33 +127,29 @@ lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:RAK3112_companion_radio_usb] +[env:RAK_3112_companion_radio_usb] extends = rak3112 build_flags = ${rak3112.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - + - + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:RAK3112_companion_radio_ble] +[env:RAK_3112_companion_radio_ble] extends = rak3112 build_flags = ${rak3112.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D AUTO_SHUTDOWN_MILLIVOLTS=3400 -D BLE_DEBUG_LOGGING=1 @@ -169,8 +157,6 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - + - + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -178,14 +164,13 @@ lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:RAK3112_companion_radio_wifi] +[env:RAK_3112_companion_radio_wifi] extends = rak3112 build_flags = ${rak3112.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=SSD1306Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' @@ -193,8 +178,6 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - + - + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -202,7 +185,7 @@ lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:RAK3112_sensor] +[env:RAK_3112_sensor] extends = rak3112 build_flags = ${rak3112.build_flags} @@ -212,11 +195,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ENV_PIN_SDA=33 -D ENV_PIN_SCL=34 - -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - + +<../examples/simple_sensor> lib_deps = ${rak3112.lib_deps} diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 30d35d0b40..7467ceb9c0 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -6,7 +6,6 @@ build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I variants/rak3401 -D RAK_3401 - -D RAK13302 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/rak3x72/platformio.ini b/variants/rak3x72/platformio.ini index a626008944..12ea413ab9 100644 --- a/variants/rak3x72/platformio.ini +++ b/variants/rak3x72/platformio.ini @@ -3,6 +3,7 @@ extends = stm32_base board = rak3172 board_upload.maximum_size = 229376 ; 32kb for FS build_flags = ${stm32_base.build_flags} + -D RAK_3X72 -D RADIO_CLASS=CustomSTM32WLx -D WRAPPER_CLASS=CustomSTM32WLxWrapper -D SPI_INTERFACES_COUNT=0 @@ -13,7 +14,7 @@ build_flags = ${stm32_base.build_flags} build_src_filter = ${stm32_base.build_src_filter} +<../variants/rak3x72> -[env:rak3x72_repeater] +[env:RAK_3x72_repeater] extends = rak3x72 build_flags = ${rak3x72.build_flags} -D ADVERT_NAME='"RAK3x72 Repeater"' @@ -22,7 +23,7 @@ build_flags = ${rak3x72.build_flags} build_src_filter = ${rak3x72.build_src_filter} +<../examples/simple_repeater/*.cpp> -[env:rak3x72_sensor] +[env:RAK_3x72_sensor] extends = rak3x72 build_flags = ${rak3x72.build_flags} -D ADVERT_NAME='"RAK3x72 Sensor"' @@ -30,7 +31,7 @@ build_flags = ${rak3x72.build_flags} build_src_filter = ${rak3x72.build_src_filter} +<../examples/simple_sensor> -[env:rak3x72_companion_radio_usb] +[env:RAK_3x72_companion_radio_usb] extends = rak3x72 build_flags = ${rak3x72.build_flags} ; -D FORMAT_FS=true From 31a2e74ada0b644ad955b19b872ddf662849fbbc Mon Sep 17 00:00:00 2001 From: Thane Gill Date: Sat, 7 Feb 2026 16:56:20 -0800 Subject: [PATCH 344/409] Correct manufacturer name 'Elecrow ThinkNode M5' --- variants/thinknode_m5/ThinknodeM5Board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 5adc8c0059..c4de538c56 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -43,5 +43,5 @@ void ThinknodeM5Board::begin() { } const char* ThinknodeM5Board::getManufacturerName() const { - return "Elecrow ThinkNode M2"; + return "Elecrow ThinkNode M5"; } From 3ff1394dd231a1320a8cdc67e7b5ddbcb1ead31c Mon Sep 17 00:00:00 2001 From: Thane Gill Date: Sun, 8 Feb 2026 14:49:03 -0800 Subject: [PATCH 345/409] build.sh: add list and -l to list firmwares available to build. --- build.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/build.sh b/build.sh index b7f95dd742..2b7c482462 100755 --- a/build.sh +++ b/build.sh @@ -7,6 +7,7 @@ sh build.sh [target] Commands: help|usage|-h|--help: Shows this message. + list|-l: List firmwares available to build. build-firmware : Build the firmware for the given build target. build-firmwares: Build all firmwares for all targets. build-matching-firmwares : Build all firmwares for build targets containing the string given for . @@ -46,20 +47,24 @@ $ sh build.sh build-firmware RAK_4631_repeater EOF } +# get a list of pio env names that start with "env:" +get_pio_envs() { + pio project config | grep 'env:' | sed 's/env://' +} + # Catch cries for help before doing anything else. case $1 in help|usage|-h|--help) global_usage exit 1 ;; + list|-l) + get_pio_envs + exit 0 + ;; esac -# get a list of pio env names that start with "env:" -get_pio_envs() { - echo $(pio project config | grep 'env:' | sed 's/env://') -} - # $1 should be the string to find (case insensitive) get_pio_envs_containing_string() { shopt -s nocasematch From 810fd561d232e4a2d344ffdc7346ce2d370ea177 Mon Sep 17 00:00:00 2001 From: Snayler <11491485+Snayler@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:20:29 +0000 Subject: [PATCH 346/409] Enable TX LED for LilyGo LoRa32 V2.1_1.6 Working on my device, green TX LED starts blinking every time I transmit --- variants/lilygo_tlora_v2_1/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index f27f57fd9e..c28f90011e 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -18,7 +18,7 @@ build_flags = -D P_LORA_SCLK=5 ; SPI clock -D P_LORA_MISO=19 ; SPI MISO -D P_LORA_MOSI=27 ; SPI MOSI - -D P_LORA_TX_LED=2 ; LED pin for TX indication + -D P_LORA_TX_LED=25 ; LED pin for TX indication -D PIN_BOARD_SDA=21 -D PIN_BOARD_SCL=22 -D PIN_VBAT_READ=35 ; Battery voltage reading (analog pin) @@ -191,4 +191,4 @@ build_flags = ; -D CORE_DEBUG_LEVEL=3 lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} From bafa2ccd2220fc194f9df64d26521458b711394a Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 10 Feb 2026 17:01:30 +1300 Subject: [PATCH 347/409] fix estimated timeout for multi byte path traces --- examples/companion_radio/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9671609156..a9ac1cf0ad 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1568,7 +1568,7 @@ void MyMesh::handleCmdFrame(size_t len) { sendDirect(pkt, &cmd_frame[10], path_len); uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); - uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); + uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len >> path_sz); out_frame[0] = RESP_CODE_SENT; out_frame[1] = 0; From f720338c03b25a01a9c14ec0dd293243f290cc4c Mon Sep 17 00:00:00 2001 From: dylan <1577856435@qq.com> Date: Wed, 11 Feb 2026 14:12:48 +0800 Subject: [PATCH 348/409] Fix WioTrackerL1 BLE companion: route sensors to Grove I2C bus (Wire1) Sensors connected via the Grove I2C connector (D18/D17) were not detected because the firmware scanned the OLED I2C bus (Wire, D14/D15) by default. Adding ENV_PIN_SDA/SCL flags directs EnvironmentSensorManager to use Wire1, matching the physical Grove connector pinout. Co-Authored-By: Claude Opus 4.6 --- variants/wio-tracker-l1/platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 75651d6957..da760b517d 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -96,6 +96,8 @@ build_flags = ${WioTrackerL1.build_flags} -D PIN_BUZZER=12 -D QSPIFLASH=1 -D ADVERT_NAME='"@@MAC"' + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${WioTrackerL1.build_src_filter} From beff18c53bf98f63dd72825f14001962f2cb6a6b Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Wed, 11 Feb 2026 09:34:41 +0100 Subject: [PATCH 349/409] fix usb and build for rak 3112 --- variants/rak3112/platformio.ini | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index bc43ee04dc..d030e74946 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -7,6 +7,7 @@ build_flags = -I variants/rak3112 -D RAK_3112=1 -D ESP32_CPU_FREQ=80 + -D ARDUINO_USB_CDC_ON_BOOT=1 -D P_LORA_DIO_1=47 -D P_LORA_NSS=7 -D P_LORA_RESET=8 @@ -131,14 +132,14 @@ lib_deps = extends = rak3112 build_flags = ${rak3112.build_flags} - -I examples/companion_radio/ui-new + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -147,7 +148,7 @@ lib_deps = extends = rak3112 build_flags = ${rak3112.build_flags} - -I examples/companion_radio/ui-new + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 ; dynamic, random PIN @@ -159,7 +160,7 @@ build_flags = build_src_filter = ${rak3112.build_src_filter} + +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -168,7 +169,7 @@ lib_deps = extends = rak3112 build_flags = ${rak3112.build_flags} - -I examples/companion_radio/ui-new + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D WIFI_DEBUG_LOGGING=1 @@ -180,7 +181,7 @@ build_flags = build_src_filter = ${rak3112.build_src_filter} + +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 From fb025fb67e7d23b6eaaa3118b89d19f9e5c36b2a Mon Sep 17 00:00:00 2001 From: Leah Date: Wed, 11 Feb 2026 09:51:28 +0100 Subject: [PATCH 350/409] Add muted icon to show when buzzer is muted --- examples/companion_radio/ui-new/UITask.cpp | 8 ++++++++ examples/companion_radio/ui-new/UITask.h | 8 ++++++++ examples/companion_radio/ui-new/icons.h | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ae2d93753f..265532be0b 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -131,6 +131,14 @@ class HomeScreen : public UIScreen { // fill the battery based on the percentage int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100; display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4); + + // show muted icon if buzzer is muted +#ifdef PIN_BUZZER + if (_task->isBuzzerQuiet()) { + display.setColor(DisplayDriver::RED); + display.drawXbm(iconX - 9, iconY + 1, muted_icon, 8, 8); + } +#endif } CayenneLPP sensors_lpp; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 02c3cafbd1..a77ad6e7ec 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -78,6 +78,14 @@ class UITask : public AbstractUITask { bool hasDisplay() const { return _display != NULL; } bool isButtonPressed() const; + bool isBuzzerQuiet() { +#ifdef PIN_BUZZER + return buzzer.isQuiet(); +#else + return true; +#endif + } + void toggleBuzzer(); bool getGPSState(); void toggleGPS(); diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index 5220f4090c..cbe237902d 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -115,4 +115,8 @@ static const uint8_t advert_icon[] = { 0x38, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x30, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t muted_icon[] = { + 0x20, 0x6a, 0xea, 0xe4, 0xe4, 0xea, 0x6a, 0x20 }; \ No newline at end of file From 77675ab4966a6efaf40e046585b4cd682f0aed11 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 6 Feb 2026 14:59:49 +1100 Subject: [PATCH 351/409] add -D ESP32_PLATFORM to esp32_base --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 69883271d3..c47e757eee 100644 --- a/platformio.ini +++ b/platformio.ini @@ -59,6 +59,7 @@ platform = platformio/espressif32@6.11.0 monitor_filters = esp32_exception_decoder extra_scripts = merge-bin.py build_flags = ${arduino_base.build_flags} + -D ESP32_PLATFORM ; -D ESP32_CPU_FREQ=80 ; change it to your need build_src_filter = ${arduino_base.build_src_filter} From 5df139f3d634a9666e3b2d4d9fb40116ac3e0960 Mon Sep 17 00:00:00 2001 From: taco Date: Fri, 13 Feb 2026 12:43:04 +1100 Subject: [PATCH 352/409] update build.sh to support RP2040 and STM32 --- build.sh | 55 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/build.sh b/build.sh index 2b7c482462..313c4c47a0 100755 --- a/build.sh +++ b/build.sh @@ -64,6 +64,8 @@ case $1 in ;; esac +# cache project config json for use in get_platform_for_env() +PIO_CONFIG_JSON=$(pio project config --json-output) # $1 should be the string to find (case insensitive) get_pio_envs_containing_string() { @@ -87,6 +89,25 @@ get_pio_envs_ending_with_string() { done } +# get platform flag for a given environment +# $1 should be the environment name +get_platform_for_env() { + local env_name=$1 + echo "$PIO_CONFIG_JSON" | python3 -c " +import sys, json, re +data = json.load(sys.stdin) +for section, options in data: + if section == 'env:$env_name': + for key, value in options: + if key == 'build_flags': + for flag in value: + match = re.search(r'(ESP32_PLATFORM|NRF52_PLATFORM|STM32_PLATFORM|RP2040_PLATFORM)', flag) + if match: + print(match.group(1)) + sys.exit(0) +" +} + # disable all debug logging flags if DISABLE_DEBUG=1 is set disable_debug_flags() { if [ "$DISABLE_DEBUG" == "1" ]; then @@ -96,6 +117,8 @@ disable_debug_flags() { # build firmware for the provided pio env in $1 build_firmware() { + # get env platform for post build actions + ENV_PLATFORM=($(get_platform_for_env $1)) # get git commit sha COMMIT_HASH=$(git rev-parse --short HEAD) @@ -126,27 +149,31 @@ build_firmware() { # build firmware target pio run -e $1 - # build merge-bin for esp32 fresh install - if [ -f .pio/build/$1/firmware.bin ]; then + # build merge-bin for esp32 fresh install, copy .bins to out folder (e.g: Heltec_v3_room_server-v1.0.0-SHA.bin) + if [ "$ENV_PLATFORM" == "ESP32_PLATFORM" ]; then pio run -t mergebin -e $1 + cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true + cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true fi - # build .uf2 for nrf52 boards - if [[ -f .pio/build/$1/firmware.zip && -f .pio/build/$1/firmware.hex ]]; then + # build .uf2 for nrf52 boards, copy .uf2 and .zip to out folder (e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2) + if [ "$ENV_PLATFORM" == "NRF52_PLATFORM" ]; then python3 bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840 + cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true + cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true fi - # copy .bin, .uf2, and .zip to out folder - # e.g: Heltec_v3_room_server-v1.0.0-SHA.bin - # e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2 - - # copy .bin for esp32 boards - cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true - cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true + # for stm32, copy .bin and .hex to out folder + if [ "$ENV_PLATFORM" == "STM32_PLATFORM" ]; then + cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true + cp .pio/build/$1/firmware.hex out/${FIRMWARE_FILENAME}.hex 2>/dev/null || true + fi - # copy .zip and .uf2 of nrf52 boards - cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true - cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true + # for rp2040, copy .bin and .uf2 to out folder + if [ "$ENV_PLATFORM" == "RP2040_PLATFORM" ]; then + cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true + cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true + fi } From 564a19d125a332bfe8cf20be8d66c792af88c2b2 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 14 Feb 2026 15:50:06 +1100 Subject: [PATCH 353/409] * companion client repeat mode support --- examples/companion_radio/DataStore.cpp | 4 +-- examples/companion_radio/MyMesh.cpp | 42 +++++++++++++++++++++++++- examples/companion_radio/MyMesh.h | 4 ++- examples/companion_radio/NodePrefs.h | 1 + 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index c0f2c0212c..1239ea3d69 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -212,7 +212,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.read(pad, 1); // 62 + file.read((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62 file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 @@ -247,7 +247,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.write(pad, 1); // 62 + file.write((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62 file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a9ac1cf0ad..03a55cd8c4 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -56,6 +56,7 @@ #define CMD_SEND_ANON_REQ 57 #define CMD_SET_AUTOADD_CONFIG 58 #define CMD_GET_AUTOADD_CONFIG 59 +#define CMD_GET_ALLOWED_REPEAT_FREQ 60 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -88,6 +89,7 @@ #define RESP_CODE_TUNING_PARAMS 23 #define RESP_CODE_STATS 24 // v8+, second byte is stats type #define RESP_CODE_AUTOADD_CONFIG 25 +#define RESP_ALLOWED_REPEAT_FREQ 26 #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -455,6 +457,10 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) { return false; } +bool MyMesh::allowPacketForward(const mesh::Packet* packet) { + return _prefs.client_repeat != 0; +} + void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { // TODO: dynamic send_scope, depending on recipient and current 'home' Region if (send_scope.isNull()) { @@ -881,6 +887,24 @@ uint32_t MyMesh::getBLEPin() { return _active_ble_pin; } +struct FreqRange { + uint32_t lower_freq, upper_freq; +}; + +static FreqRange repeat_freq_ranges[] = { + { 433000, 433000 }, + { 869000, 869000 }, + { 918000, 918000 } +}; + +bool MyMesh::isValidClientRepeatFreq(uint32_t f) const { + for (int i = 0; i < sizeof(repeat_freq_ranges)/sizeof(repeat_freq_ranges[0]); i++) { + auto r = &repeat_freq_ranges[i]; + if (f >= r->lower_freq && f <= r->upper_freq) return true; + } + return false; +} + void MyMesh::startInterface(BaseSerialInterface &serial) { _serial = &serial; serial.enable(); @@ -1208,13 +1232,20 @@ void MyMesh::handleCmdFrame(size_t len) { i += 4; uint8_t sf = cmd_frame[i++]; uint8_t cr = cmd_frame[i++]; + uint8_t repeat = 0; // default - false + if (len > i) { + repeat = cmd_frame[i++]; // FIRMWARE_VER_CODE 9+ + } - if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && + if (repeat && !isValidClientRepeatFreq(freq)) { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); + } else if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && bw <= 500000) { _prefs.sf = sf; _prefs.cr = cr; _prefs.freq = (float)freq / 1000.0; _prefs.bw = (float)bw / 1000.0; + _prefs.client_repeat = repeat; savePrefs(); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); @@ -1741,6 +1772,15 @@ void MyMesh::handleCmdFrame(size_t len) { out_frame[i++] = RESP_CODE_AUTOADD_CONFIG; out_frame[i++] = _prefs.autoadd_config; _serial->writeFrame(out_frame, i); + } else if (cmd_frame[0] == CMD_GET_ALLOWED_REPEAT_FREQ) { + int i = 0; + out_frame[i++] = RESP_ALLOWED_REPEAT_FREQ; + for (int k = 0; k < sizeof(repeat_freq_ranges)/sizeof(repeat_freq_ranges[0]) && i + 8 < sizeof(out_frame); k++) { + auto r = &repeat_freq_ranges[k]; + memcpy(&out_frame[i], &r->lower_freq, 4); i += 4; + memcpy(&out_frame[i], &r->upper_freq, 4); i += 4; + } + _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 95265a19ac..ff549771f6 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -5,7 +5,7 @@ #include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 8 +#define FIRMWARE_VER_CODE 9 #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "29 Jan 2026" @@ -108,6 +108,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { int calcRxDelay(float score, uint32_t air_time) const override; uint8_t getExtraAckTransmitCount() const override; bool filterRecvFloodPacket(mesh::Packet* packet) override; + bool allowPacketForward(const mesh::Packet* packet) override; void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override; void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override; @@ -176,6 +177,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { void checkCLIRescueCmd(); void checkSerialInterface(); + bool isValidClientRepeatFreq(uint32_t f) const; // helpers, short-cuts void saveChannels() { _store->saveChannels(this); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index d7ddd92a51..f2a52f41e5 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -28,4 +28,5 @@ struct NodePrefs { // persisted to file uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) uint32_t gps_interval; // GPS read interval in seconds uint8_t autoadd_config; // bitmask for auto-add contacts config + uint8_t client_repeat; }; \ No newline at end of file From 0abac357445ff790e7b9b58d1987a14d24e13728 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 14 Feb 2026 16:45:41 +1100 Subject: [PATCH 354/409] * client_repeat state now in _DEVICE_INFO response --- examples/companion_radio/MyMesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 03a55cd8c4..87d3091a0f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -928,6 +928,7 @@ void MyMesh::handleCmdFrame(size_t len) { i += 40; StrHelper::strzcpy((char *)&out_frame[i], FIRMWARE_VERSION, 20); i += 20; + out_frame[i++] = _prefs.client_repeat; // v9+ _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_APP_START && len >= 8) { // sent when app establishes connection, respond with node ID From e2571accbec7a24cd73e71bdd01c2ca1f091f6a8 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 15 Feb 2026 17:24:37 +1100 Subject: [PATCH 355/409] * ver 1.13.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index ff549771f6..1c5813eb7e 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 9 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "29 Jan 2026" +#define FIRMWARE_BUILD_DATE "15 Feb 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.12.0" +#define FIRMWARE_VERSION "v1.13.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 7a51b4a97e..8388e29cea 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -69,11 +69,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "29 Jan 2026" + #define FIRMWARE_BUILD_DATE "15 Feb 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.12.0" + #define FIRMWARE_VERSION "v1.13.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index b4529e7762..d21e225f25 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "29 Jan 2026" + #define FIRMWARE_BUILD_DATE "15 Feb 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.12.0" + #define FIRMWARE_VERSION "v1.13.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 4bc0d784ee..7131db751d 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "29 Jan 2026" + #define FIRMWARE_BUILD_DATE "15 Feb 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.12.0" + #define FIRMWARE_VERSION "v1.13.0" #endif #define FIRMWARE_ROLE "sensor" From cafc212bb2171b316a34f47e229d4dc42dfbbb65 Mon Sep 17 00:00:00 2001 From: recrof Date: Sun, 15 Feb 2026 11:25:27 +0100 Subject: [PATCH 356/409] fix M5Stack Unit M6L build errors --- src/helpers/esp32/SerialBLEInterface.cpp | 1 + variants/m5stack_unit_c6l/platformio.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index eccfeca684..dcfa0e1e34 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -1,4 +1,5 @@ #include "SerialBLEInterface.h" +#include "esp_mac.h" // See the following for generating UUIDs: // https://www.uuidgenerator.net/ diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini index bbfdb4a119..a2b8b08736 100644 --- a/variants/m5stack_unit_c6l/platformio.ini +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -5,7 +5,7 @@ board_build.partitions = min_spiffs.csv ; get around 4mb flash limit build_flags = ${esp32c6_base.build_flags} ${sensor_base.build_flags} - -I variants/M5Stack_Unit_C6L + -I variants/m5stack_unit_c6l -D P_LORA_TX_LED=15 -D P_LORA_SCLK=20 -D P_LORA_MISO=22 From e8785dd9b0fd4ceace644f19d68d42ff8c8a8cbc Mon Sep 17 00:00:00 2001 From: realtag Date: Mon, 16 Feb 2026 22:35:20 +0000 Subject: [PATCH 357/409] discover sends a single repeater discovery request and populates the neighbor list; self is excluded --- examples/simple_repeater/MyMesh.cpp | 40 +++++++++++++++++++++++++++++ examples/simple_repeater/MyMesh.h | 1 + 2 files changed, 41 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 65e0cee52f..e84ce08e4d 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -738,6 +738,37 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this } } + } else if (type == CTL_TYPE_NODE_DISCOVER_RESP && packet->payload_len >= 6) { + uint8_t node_type = packet->payload[0] & 0x0F; + if (node_type != ADV_TYPE_REPEATER) { + return; + } + if (packet->payload_len < 6 + PUB_KEY_SIZE) { + MESH_DEBUG_PRINTLN("onControlDataRecv: DISCOVER_RESP pubkey too short: %d", (uint32_t)packet->payload_len); + return; + } + + mesh::Identity id(&packet->payload[6]); + if (id.matches(self_id)) { + return; + } + putNeighbour(id, rtc_clock.getCurrentTime(), packet->getSNR()); + } +} + +void MyMesh::sendNodeDiscoverReq() { + if (_prefs.disable_fwd) return; + + uint8_t data[10]; + data[0] = CTL_TYPE_NODE_DISCOVER_REQ; // prefix_only=0 + data[1] = (1 << ADV_TYPE_REPEATER); + getRNG()->random(&data[2], 4); // tag + uint32_t since = 0; + memcpy(&data[6], &since, 4); + + auto pkt = createControlData(data, sizeof(data)); + if (pkt) { + sendZeroHop(pkt); } } @@ -1168,6 +1199,15 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - ??"); } + } else if (memcmp(command, "discover", 8) == 0) { + const char* sub = command + 8; + while (*sub == ' ') sub++; + if (*sub != 0) { + strcpy(reply, "Err - discover has no options"); + } else { + sendNodeDiscoverReq(); + strcpy(reply, "OK - Discover sent"); + } } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 8388e29cea..6cded9cfc1 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -116,6 +116,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); + void sendNodeDiscoverReq(); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); From 87c78a98bdef070b60f694eb8c4f43fb6bd57d83 Mon Sep 17 00:00:00 2001 From: realtag Date: Tue, 17 Feb 2026 01:04:14 +0000 Subject: [PATCH 358/409] discover.neighbors sends a tagged repeater discovery request and only accepts matching repeater responses --- examples/simple_repeater/MyMesh.cpp | 21 ++++++++++++++++++--- examples/simple_repeater/MyMesh.h | 2 ++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index e84ce08e4d..aec4ff3e64 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -748,6 +748,16 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { return; } + if (pending_discover_tag == 0 || millisHasNowPassed(pending_discover_until)) { + pending_discover_tag = 0; + return; + } + uint32_t tag; + memcpy(&tag, &packet->payload[2], 4); + if (tag != pending_discover_tag) { + return; + } + mesh::Identity id(&packet->payload[6]); if (id.matches(self_id)) { return; @@ -763,6 +773,8 @@ void MyMesh::sendNodeDiscoverReq() { data[0] = CTL_TYPE_NODE_DISCOVER_REQ; // prefix_only=0 data[1] = (1 << ADV_TYPE_REPEATER); getRNG()->random(&data[2], 4); // tag + memcpy(&pending_discover_tag, &data[2], 4); + pending_discover_until = futureMillis(30000); uint32_t since = 0; memcpy(&data[6], &since, 4); @@ -832,6 +844,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.advert_loc_policy = ADVERT_LOC_PREFS; _prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier + + pending_discover_tag = 0; + pending_discover_until = 0; } void MyMesh::begin(FILESYSTEM *fs) { @@ -1199,11 +1214,11 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - ??"); } - } else if (memcmp(command, "discover", 8) == 0) { - const char* sub = command + 8; + } else if (memcmp(command, "discover.neighbors", 18) == 0) { + const char* sub = command + 18; while (*sub == ' ') sub++; if (*sub != 0) { - strcpy(reply, "Err - discover has no options"); + strcpy(reply, "Err - discover.neighbors has no options"); } else { sendNodeDiscoverReq(); strcpy(reply, "OK - Discover sent"); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 6cded9cfc1..f0e7cc10ea 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -97,6 +97,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; RateLimiter discover_limiter, anon_limiter; + uint32_t pending_discover_tag; + unsigned long pending_discover_until; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS From bf9c6cb50f91de89dfbc7dffc24653f68816eda7 Mon Sep 17 00:00:00 2001 From: realtag Date: Tue, 17 Feb 2026 01:22:17 +0000 Subject: [PATCH 359/409] Increased the timeout timer to 60 seconds, up from 30 seconds. --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index aec4ff3e64..e2bf033026 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -774,7 +774,7 @@ void MyMesh::sendNodeDiscoverReq() { data[1] = (1 << ADV_TYPE_REPEATER); getRNG()->random(&data[2], 4); // tag memcpy(&pending_discover_tag, &data[2], 4); - pending_discover_until = futureMillis(30000); + pending_discover_until = futureMillis(60000); uint32_t since = 0; memcpy(&data[6], &since, 4); From 0770618ee2329efaead80bfb78986ee459fd2db4 Mon Sep 17 00:00:00 2001 From: realtag Date: Tue, 17 Feb 2026 01:39:04 +0000 Subject: [PATCH 360/409] Allow repeater discovery even if repeater mode is disabled on the requesting repeater. --- examples/simple_repeater/MyMesh.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index e2bf033026..20be10102b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -767,8 +767,6 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { } void MyMesh::sendNodeDiscoverReq() { - if (_prefs.disable_fwd) return; - uint8_t data[10]; data[0] = CTL_TYPE_NODE_DISCOVER_REQ; // prefix_only=0 data[1] = (1 << ADV_TYPE_REPEATER); From 3e53df5082ec9da139c2ecd1dfd067ceadeff63a Mon Sep 17 00:00:00 2001 From: 3DPGG <3dpgg@protonmail.com> Date: Mon, 16 Feb 2026 17:41:52 -0800 Subject: [PATCH 361/409] Fix LilyGo_TLora_V2_1_1_6_terminal_chat build This change addresses two issues. The first is that the LilyGo_TLora_V2_1_1_6_terminal_chat build would try to compile simple_repeater/MyMesh.cpp. All other examples of terminal chat targets are instead building simple_secure_chat/main.cpp . This change would align this build to the rest of the builds. The second issue, found during the course of investigating the first, stems from simple_repeater/MyMesh.cpp using the MAX_NEIGHBOURS #define to control whether the neighbor list is kept. Repeaters that keep this list must define this value, and if the value is not defined, then all neighbor-related functionality is compiled out. However, the code that replies to REQ_TYPE_GET_NEIGHBOURS did not properly check for this #define, and thus any target that compiles simple_repeater/MyMesh.cpp without defining MAX_NEIGHBOURS would get an undefined variable compilation error. As a practical matter though, there are no targets that compile simple_repeater/MyMesh.cpp AND do not define MAX_NEIGHBOURS, except this build due to the first issue. As a result, the second issue is addressed only as a matter of completeness. The expected behavior with this change is that such a repeater would send a valid reply indicating zero known neighbors. --- examples/simple_repeater/MyMesh.cpp | 4 ++++ variants/lilygo_tlora_v2_1/platformio.ini | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 65e0cee52f..edbc2c4296 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -292,6 +292,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t // create copy of neighbours list, skipping empty entries so we can sort it separately from main list int16_t neighbours_count = 0; +#if MAX_NEIGHBOURS NeighbourInfo* sorted_neighbours[MAX_NEIGHBOURS]; for (int i = 0; i < MAX_NEIGHBOURS; i++) { auto neighbour = &neighbours[i]; @@ -327,6 +328,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return a->snr < b->snr; // asc }); } +#endif // build results buffer int results_count = 0; @@ -341,6 +343,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t break; } +#if MAX_NEIGHBOURS // add next neighbour to results auto neighbour = sorted_neighbours[index + offset]; uint32_t heard_seconds_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; @@ -348,6 +351,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t memcpy(&results_buffer[results_offset], &heard_seconds_ago, 4); results_offset += 4; memcpy(&results_buffer[results_offset], &neighbour->snr, 1); results_offset += 1; results_count++; +#endif } diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index c28f90011e..7e1330e6cc 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -65,7 +65,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - +<../examples/simple_repeater> + +<../examples/simple_secure_chat/main.cpp> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 From 5de3e1bf32fd1a9f1d1966c92f94b5414ef44b84 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 17 Feb 2026 20:10:13 +1100 Subject: [PATCH 362/409] * repeater: slight increase to default direct.txdelay --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index edbc2c4296..692fcafebd 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -775,7 +775,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f - _prefs.direct_tx_delay_factor = 0.2f; // was zero + _prefs.direct_tx_delay_factor = 0.3f; // was 0.2 StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; From 2e0029812883c14485833064ec2e8ed401dc17c9 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 17 Feb 2026 20:25:56 +1100 Subject: [PATCH 363/409] * companion: retransmit delays now hard-coded (only for client repeat mode) --- examples/companion_radio/MyMesh.cpp | 9 +++++++++ examples/companion_radio/MyMesh.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 87d3091a0f..99b14952f4 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -257,6 +257,15 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); } +uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.5f); + return getRNG()->nextInt(0, 5*t + 1); +} +uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.2f); + return getRNG()->nextInt(0, 5*t + 1); +} + uint8_t MyMesh::getExtraAckTransmitCount() const { return _prefs.multi_acks; } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 1c5813eb7e..e3c109859e 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -106,6 +106,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { float getAirtimeBudgetFactor() const override; int getInterferenceThreshold() const override; int calcRxDelay(float score, uint32_t air_time) const override; + uint32_t getRetransmitDelay(const mesh::Packet *packet) override; + uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override; uint8_t getExtraAckTransmitCount() const override; bool filterRecvFloodPacket(mesh::Packet* packet) override; bool allowPacketForward(const mesh::Packet* packet) override; From ffc9815e9a26a01245f6032fa341d0ba88d8b41d Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Tue, 17 Feb 2026 23:54:33 +0100 Subject: [PATCH 364/409] Fix packet pool leak when rx queue is full MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PacketQueue::add() silently dropped packets when the queue was at capacity. The packet pointer was lost — never enqueued, never returned to the unused pool. Each occurrence permanently shrank the 32-packet pool until allocNew() returned NULL and the node went deaf. Return bool from add() and free the packet back to the pool on failure. --- src/helpers/StaticPoolPacketManager.cpp | 16 +++++++++++----- src/helpers/StaticPoolPacketManager.h | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 4f28eac6a7..125efb7582 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -55,15 +55,15 @@ mesh::Packet* PacketQueue::removeByIdx(int i) { return item; } -void PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { +bool PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { if (_num == _size) { - // TODO: log "FATAL: queue is full!" - return; + return false; } _table[_num] = packet; _pri_table[_num] = priority; _schedule_table[_num] = scheduled_for; _num++; + return true; } StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): unused(pool_size), send_queue(pool_size), rx_queue(pool_size) { @@ -82,7 +82,10 @@ void StaticPoolPacketManager::free(mesh::Packet* packet) { } void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { - send_queue.add(packet, priority, scheduled_for); + if (!send_queue.add(packet, priority, scheduled_for)) { + MESH_DEBUG_PRINTLN("queueOutbound: send queue full, dropping packet"); + free(packet); + } } mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) { @@ -106,7 +109,10 @@ mesh::Packet* StaticPoolPacketManager::removeOutboundByIdx(int i) { } void StaticPoolPacketManager::queueInbound(mesh::Packet* packet, uint32_t scheduled_for) { - rx_queue.add(packet, 0, scheduled_for); + if (!rx_queue.add(packet, 0, scheduled_for)) { + MESH_DEBUG_PRINTLN("queueInbound: rx queue full, dropping packet"); + free(packet); + } } mesh::Packet* StaticPoolPacketManager::getNextInbound(uint32_t now) { return rx_queue.get(now); diff --git a/src/helpers/StaticPoolPacketManager.h b/src/helpers/StaticPoolPacketManager.h index bbf4b193d3..52c299dbc4 100644 --- a/src/helpers/StaticPoolPacketManager.h +++ b/src/helpers/StaticPoolPacketManager.h @@ -11,7 +11,7 @@ class PacketQueue { public: PacketQueue(int max_entries); mesh::Packet* get(uint32_t now); - void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for); + bool add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for); int count() const { return _num; } int countBefore(uint32_t now) const; mesh::Packet* itemAt(int i) const { return _table[i]; } From 1500a5a9cbfe2df8bbd06ba08e5e58bd5f319f5e Mon Sep 17 00:00:00 2001 From: taco Date: Wed, 18 Feb 2026 15:35:20 +1100 Subject: [PATCH 365/409] add get bootloader.ver command for nrf52 --- src/MeshCore.h | 1 + src/helpers/CommonCLI.cpp | 11 +++++++++++ src/helpers/NRF52Board.cpp | 19 +++++++++++++++++++ src/helpers/NRF52Board.h | 1 + 4 files changed, 32 insertions(+) diff --git a/src/MeshCore.h b/src/MeshCore.h index f194cdeb43..70cd0f0672 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -55,6 +55,7 @@ class MainBoard { virtual uint32_t getGpio() { return 0; } virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; + virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; } virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported // Power management interface (boards with power management override these) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 6dcf7018e1..263eb66522 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -362,6 +362,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "bridge.secret", 13) == 0) { sprintf(reply, "> %s", _prefs->bridge_secret); #endif + } else if (memcmp(config, "bootloader.ver", 14) == 0) { + #ifdef NRF52_PLATFORM + char ver[32]; + if (_board->getBootloaderVersion(ver, sizeof(ver))) { + sprintf(reply, "> %s", ver); + } else { + strcpy(reply, "> unknown"); + } + #else + strcpy(reply, "ERROR: unsupported"); + #endif } else if (memcmp(config, "adc.multiplier", 14) == 0) { float adc_mult = _board->getAdcMultiplier(); if (adc_mult == 0.0f) { diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 1db858f508..2c8753d464 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -297,6 +297,25 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } +bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) { + static const char BOOTLOADER_MARKER[] = "UF2 Bootloader "; + const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B + + for (uint32_t i = 0; i < 0x3000 - (sizeof(BOOTLOADER_MARKER) - 1); i++) { + if (memcmp(&flash[i], BOOTLOADER_MARKER, sizeof(BOOTLOADER_MARKER) - 1) == 0) { + const char* ver = (const char*)&flash[i + sizeof(BOOTLOADER_MARKER) - 1]; + size_t len = 0; + while (len < max_len - 1 && ver[len] != '\0' && ver[len] != ' ' && ver[len] != '\n' && ver[len] != '\r') { + out[len] = ver[len]; + len++; + } + out[len] = '\0'; + return len > 0; // bootloader string is non-empty + } + } + return false; +} + bool NRF52Board::startOTAUpdate(const char *id, char reply[]) { // Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 0332af0781..96f67dc950 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -50,6 +50,7 @@ class NRF52Board : public mesh::MainBoard { virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + virtual bool getBootloaderVersion(char* version, size_t max_len) override; virtual bool startOTAUpdate(const char *id, char reply[]) override; virtual void sleep(uint32_t secs) override; From 063f5056f23b7a3999016f6b60028bd724c3837b Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Mon, 2 Feb 2026 11:21:00 +0700 Subject: [PATCH 366/409] Fixed RefCountedDigitalPin.h to release claim correctly. Ensure no negative claims number. --- src/helpers/RefCountedDigitalPin.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h index 753f6c30e9..4cf53cda4e 100644 --- a/src/helpers/RefCountedDigitalPin.h +++ b/src/helpers/RefCountedDigitalPin.h @@ -20,10 +20,12 @@ class RefCountedDigitalPin { digitalWrite(_pin, _active); } } + void release() { - _claims--; if (_claims == 0) { digitalWrite(_pin, !_active); + } else { + _claims--; } } }; From 39fb2902ec5653971a62fb308b9d2e56e77f7480 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 5 Feb 2026 22:42:02 +0700 Subject: [PATCH 367/409] Avoid negative _claims --- src/helpers/RefCountedDigitalPin.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h index 4cf53cda4e..f30c4c58be 100644 --- a/src/helpers/RefCountedDigitalPin.h +++ b/src/helpers/RefCountedDigitalPin.h @@ -22,10 +22,11 @@ class RefCountedDigitalPin { } void release() { + if (_claims == 0) return; // avoid negative _claims + + _claims--; if (_claims == 0) { digitalWrite(_pin, !_active); - } else { - _claims--; } } }; From f6603fe7a5edf8b8197e45b7e909c6e781507511 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 5 Feb 2026 23:26:08 +0700 Subject: [PATCH 368/409] Set back PIN_VEXT_EN_ACTIVE=HIGH --- variants/heltec_v4/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c5011e0e9b..fdddcd5d0c 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -22,7 +22,7 @@ build_flags = -D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low) -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=LOW + -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX From 44b80d00c202a9783773086ef93f64bc0ce85457 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 5 Feb 2026 23:27:10 +0700 Subject: [PATCH 369/409] Disabled periph_power for Heltec v4's display --- variants/heltec_v4/target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index f971cc6085..54fc05e891 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display(&(board.periph_power)); + DISPLAY_CLASS display(NULL); MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif From 13d0dff9182bf7931b9b6c87b67d7e5120301099 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 18 Feb 2026 22:29:33 +0700 Subject: [PATCH 370/409] Reverted to use GPIO 17, 18 as I2C for Heltec v4 repeater --- variants/heltec_v4/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index fdddcd5d0c..71ffc2e6a9 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -54,8 +54,6 @@ build_flags = -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 -D PIN_OLED_RESET=21 - -D ENV_PIN_SDA=4 - -D ENV_PIN_SCL=3 build_src_filter= ${Heltec_lora32_v4.build_src_filter} lib_deps = ${Heltec_lora32_v4.lib_deps} From 3e76161e9cacf739b5a575d3d897a13ce7c0c2e1 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 19 Feb 2026 14:37:51 +1100 Subject: [PATCH 371/409] * refactor of Contact/Client out_path_len (stored in files), from signed to unsigned byte (+2 squashed commits) Squashed commits: [f326e25] * misc [fa5152e] * new 'path mode' parsing in Dispatcher --- examples/companion_radio/MyMesh.cpp | 15 ++--- examples/simple_repeater/MyMesh.cpp | 61 ++++++++++-------- examples/simple_repeater/MyMesh.h | 1 + examples/simple_room_server/MyMesh.cpp | 43 ++++++------- examples/simple_secure_chat/main.cpp | 5 +- examples/simple_sensor/SensorMesh.cpp | 34 +++++----- examples/simple_sensor/SensorMesh.h | 2 +- src/Dispatcher.cpp | 86 ++++++++++++++------------ src/Dispatcher.h | 1 + src/Identity.h | 4 ++ src/Mesh.cpp | 74 +++++++++++----------- src/Mesh.h | 4 +- src/Packet.cpp | 13 +++- src/Packet.h | 8 +++ src/helpers/BaseChatMesh.cpp | 28 ++++----- src/helpers/ClientACL.cpp | 2 +- src/helpers/ClientACL.h | 4 +- src/helpers/ContactInfo.h | 4 +- 18 files changed, 222 insertions(+), 167 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 99b14952f4..f0f8e5fc92 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -258,11 +258,11 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { } uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.5f); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.5f); return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.2f); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.2f); return getRNG()->nextInt(0, 5*t + 1); } @@ -678,7 +678,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, } } -bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { +bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t _in_path_len, uint8_t* out_path, uint8_t _out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 4) { uint32_t tag; memcpy(&tag, extra, 4); @@ -785,9 +785,10 @@ uint32_t MyMesh::calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const { return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis); } uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const { + uint8_t path_hash_count = path_len & 63; return SEND_TIMEOUT_BASE_MILLIS + ((pkt_airtime_millis * DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * - (path_len + 1)); + (path_hash_count + 1)); } void MyMesh::onSendTimeout() {} @@ -1115,7 +1116,7 @@ void MyMesh::handleCmdFrame(size_t len) { } if (pkt) { if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop) - sendFlood(pkt); + sendFlood(pkt); // TODO: which path_hash_size to use?? } else { sendZeroHop(pkt); } @@ -1127,7 +1128,7 @@ void MyMesh::handleCmdFrame(size_t len) { uint8_t *pub_key = &cmd_frame[1]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); if (recipient) { - recipient->out_path_len = -1; + recipient->out_path_len = OUT_PATH_UNKNOWN; // recipient->lastmod = ?? shouldn't be needed, app already has this version of contact dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); @@ -1449,7 +1450,7 @@ void MyMesh::handleCmdFrame(size_t len) { memset(&req_data[2], 0, 3); // reserved getRNG()->random(&req_data[5], 4); // random blob to help make packet-hash unique auto save = recipient->out_path_len; // temporarily force sendRequest() to flood - recipient->out_path_len = -1; + recipient->out_path_len = OUT_PATH_UNKNOWN; int result = sendRequest(*recipient, req_data, sizeof(req_data), tag, est_timeout); recipient->out_path_len = save; if (result == MSG_SEND_FAILED) { diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b6d855f686..7328f7038f 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -129,7 +129,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } if (is_flood) { - client->out_path_len = -1; // need to rediscover out_path + client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path } uint32_t now = getRTCClock()->getCurrentTimeUnique(); @@ -147,9 +147,12 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data++ & 0x3F; - memcpy(reply_path, data, reply_path_len); - // data += reply_path_len; + reply_path_len = *data & 63; + reply_path_hash_size = (*data >> 6) + 1; + data++; + + memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -163,9 +166,12 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data++ & 0x3F; - memcpy(reply_path, data, reply_path_len); - // data += reply_path_len; + reply_path_len = *data & 63; + reply_path_hash_size = (*data >> 6) + 1; + data++; + + memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -180,9 +186,12 @@ uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data++ & 0x3F; - memcpy(reply_path, data, reply_path_len); - // data += reply_path_len; + reply_path_len = *data & 63; + reply_path_hash_size = (*data >> 6) + 1; + data++; + + memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -389,7 +398,7 @@ File MyMesh::openAppend(const char *fname) { bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; if (packet->isRouteFlood() && recv_pkt_region == NULL) { MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); return false; @@ -484,11 +493,11 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { } uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } @@ -538,13 +547,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else if (reply_path_len < 0) { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); + if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); + uint8_t path_len = ((reply_path_hash_size - 1) << 6) | (reply_path_len & 63); + if (reply) sendDirect(reply, reply_path, path_len, SERVER_RESPONSE_DELAY); } } } @@ -613,15 +623,15 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -651,8 +661,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, mesh::Packet *ack = createAck(ack_hash); if (ack) { - if (client->out_path_len < 0) { - sendFlood(ack, TXT_ACK_DELAY); + if (client->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(ack, TXT_ACK_DELAY, packet->getPathHashSize()); } else { sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY); } @@ -679,8 +689,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply, CLI_REPLY_DELAY_MILLIS); + if (client->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); } else { sendDirect(reply, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS); } @@ -701,7 +711,8 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); auto client = acl.getClientByIdx(i); - memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() + // store a copy of path, for sendDirect() + client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); client->last_activity = getRTCClock()->getCurrentTime(); } else { MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); @@ -906,7 +917,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis); // TODO: which path_hash_size to use?? } else { sendZeroHop(pkt, delay_millis); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f0e7cc10ea..591f636627 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -92,6 +92,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t reply_data[MAX_PACKET_PAYLOAD]; uint8_t reply_path[MAX_PATH_SIZE]; int8_t reply_path_len; + uint8_t reply_path_hash_size; TransportKeyStore key_store; RegionMap region_map, temp_map; RegionEntry* load_stack[8]; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 598b14de63..0b1d3a1ba5 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -73,13 +73,14 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len); if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply); + if (client->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(reply); // TODO: which path_hash_size to use? client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); } else { sendDirect(reply, client->out_path, client->out_path_len); - client->extra.room.ack_timeout = - futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1)); + + uint8_t path_hash_count = client->out_path_len & 63; + client->extra.room.ack_timeout = futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (path_hash_count + 1)); } _num_post_pushes++; // stats } else { @@ -264,17 +265,17 @@ const char *MyMesh::getLogDateTime() { } uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; return true; } @@ -333,7 +334,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m } if (packet->isRouteFlood()) { - client->out_path_len = -1; // need to rediscover out_path + client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path } uint32_t now = getRTCClock()->getCurrentTimeUnique(); @@ -353,14 +354,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, 13); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13); if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -448,9 +449,9 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, uint32_t delay_millis; if (send_ack) { - if (client->out_path_len < 0) { + if (client->out_path_len == OUT_PATH_UNKNOWN) { mesh::Packet *ack = createAck(ack_hash); - if (ack) sendFlood(ack, TXT_ACK_DELAY); + if (ack) sendFlood(ack, TXT_ACK_DELAY, packet->getPathHashSize()); delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS; } else { uint32_t d = TXT_ACK_DELAY; @@ -482,8 +483,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY); + if (client->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { sendDirect(reply, client->out_path, client->out_path_len, delay_millis + SERVER_RESPONSE_DELAY); } @@ -521,7 +522,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // if client sends too quickly, evict() // RULE: only send keep_alive response DIRECT! - if (client->out_path_len >= 0) { + if (client->out_path_len != OUT_PATH_UNKNOWN) { uint32_t ack_hash; // calc ACK to prove to sender that we got request mesh::Utils::sha256((uint8_t *)&ack_hash, 4, data, 9, client->id.pub_key, PUB_KEY_SIZE); @@ -538,14 +539,14 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -563,7 +564,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t if (i >= 0 && i < acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context) MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); auto client = acl.getClientByIdx(i); - memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() + client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); // store a copy of path, for sendDirect() client->last_activity = getRTCClock()->getCurrentTime(); } else { MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); @@ -679,7 +680,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis); // TODO: which path_hash_size to use? } else { sendZeroHop(pkt, delay_millis); } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index a389ec74bb..c1ed710abf 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -213,7 +213,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor { } void onContactPathUpdated(const ContactInfo& contact) override { - Serial.printf("PATH to: %s, path_len=%d\n", contact.name, (int32_t) contact.out_path_len); + Serial.printf("PATH to: %s, path_len=%d\n", contact.name, (uint32_t) contact.out_path_len); saveContacts(); } @@ -266,8 +266,9 @@ class MyMesh : public BaseChatMesh, ContactVisitor { return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis); } uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override { + uint8_t path_hash_count = path_len & 63; return SEND_TIMEOUT_BASE_MILLIS + - ( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_len + 1)); + ( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_hash_count + 1)); } void onSendTimeout() override { diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index f05fb245ce..fc9ef3101d 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -258,7 +258,7 @@ void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) { auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len); if (pkt) { - if (c->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (c->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(pkt, c->out_path, c->out_path_len); } else { sendFlood(pkt); @@ -302,7 +302,7 @@ float SensorMesh::getAirtimeBudgetFactor() const { bool SensorMesh::allowPacketForward(const mesh::Packet* packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; return true; } @@ -312,11 +312,11 @@ int SensorMesh::calcRxDelay(float score, uint32_t air_time) const { } uint32_t SensorMesh::getRetransmitDelay(const mesh::Packet* packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); return getRNG()->nextInt(0, 6)*t; } uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 6)*t; } int SensorMesh::getInterferenceThreshold() const { @@ -360,7 +360,7 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* } if (is_flood) { - client->out_path_len = -1; // need to rediscover out_path + client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path } uint32_t now = getRTCClock()->getCurrentTimeUnique(); @@ -468,10 +468,10 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); + if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -496,10 +496,10 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } } -void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash) { - if (dest.out_path_len < 0) { +void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size) { + if (dest.out_path_len == OUT_PATH_UNKNOWN) { mesh::Packet* ack = createAck(ack_hash); - if (ack) sendFlood(ack, TXT_ACK_DELAY); + if (ack) sendFlood(ack, TXT_ACK_DELAY, path_hash_size); } else { uint32_t d = TXT_ACK_DELAY; if (getExtraAckTransmitCount() > 0) { @@ -537,14 +537,14 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len); if (reply) { - if (from->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (from->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, from->out_path, from->out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -569,7 +569,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); if (path) sendFlood(path, TXT_ACK_DELAY); } else { - sendAckTo(*from, ack_hash); + sendAckTo(*from, ack_hash, packet->getPathHashSize()); } } } else if (flags == TXT_TYPE_CLI_DATA) { @@ -596,8 +596,8 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len); if (reply) { - if (from->out_path_len < 0) { - sendFlood(reply, CLI_REPLY_DELAY_MILLIS); + if (from->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); } else { sendDirect(reply, from->out_path, from->out_path_len, CLI_REPLY_DELAY_MILLIS); } @@ -666,7 +666,7 @@ bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint MESH_DEBUG_PRINTLN("PATH to contact, path_len=%d", (uint32_t) path_len); // NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. // FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?) - memcpy(from->out_path, path, from->out_path_len = path_len); // store a copy of path, for sendDirect() + from->out_path_len = mesh::Packet::copyPath(from->out_path, path, path_len); // store a copy of path, for sendDirect() from->last_activity = getRTCClock()->getCurrentTime(); // REVISIT: maybe make ALL out_paths non-persisted to minimise flash writes?? diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 7131db751d..b15a400a6a 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -128,7 +128,7 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { void onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len); - void sendAckTo(const ClientInfo& dest, uint32_t ack_hash); + void sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size=1); private: FILESYSTEM* _fs; unsigned long next_local_advert, next_flood_advert; diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 0a1549851e..12889fb866 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -108,6 +108,48 @@ void Dispatcher::loop() { checkSend(); } +bool Dispatcher::tryParsePacket(Packet* pkt, const uint8_t* raw, int len) { + int i = 0; + + pkt->header = raw[i++]; + if (pkt->getPayloadVer() > PAYLOAD_VER_1) { + MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): unsupported packet version", getLogDateTime()); + return false; + } + + if (pkt->hasTransportCodes()) { + memcpy(&pkt->transport_codes[0], &raw[i], 2); i += 2; + memcpy(&pkt->transport_codes[1], &raw[i], 2); i += 2; + } else { + pkt->transport_codes[0] = pkt->transport_codes[1] = 0; + } + + pkt->path_len = raw[i++]; + uint8_t path_mode = pkt->path_len >> 6; // upper 2 bits (legacy firmware: 00) + if (path_mode == 3) { // Reserved for future + MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): unsupported path mode: 3", getLogDateTime()); + return false; + } + + uint8_t path_byte_len = (pkt->path_len & 63) * pkt->getPathHashSize(); + if (path_byte_len > MAX_PATH_SIZE || i + path_byte_len > len) { + MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", getLogDateTime(), len); + return false; + } + + memcpy(pkt->path, &raw[i], path_byte_len); i += path_byte_len; + + pkt->payload_len = len - i; // payload is remainder + if (pkt->payload_len > sizeof(pkt->payload)) { + MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): packet payload too big, payload_len=%d", getLogDateTime(), (uint32_t)pkt->payload_len); + return false; + } + + memcpy(pkt->payload, &raw[i], pkt->payload_len); + + return true; // success +} + void Dispatcher::checkRecv() { Packet* pkt; float score; @@ -122,45 +164,14 @@ void Dispatcher::checkRecv() { if (pkt == NULL) { MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): WARNING: received data, no unused packets available!", getLogDateTime()); } else { - int i = 0; -#ifdef NODE_ID - uint8_t sender_id = raw[i++]; - if (sender_id == NODE_ID - 1 || sender_id == NODE_ID + 1) { // simulate that NODE_ID can only hear NODE_ID-1 or NODE_ID+1, eg. 3 can't hear 1 - } else { - _mgr->free(pkt); // put back into pool - return; - } -#endif - - pkt->header = raw[i++]; - if (pkt->hasTransportCodes()) { - memcpy(&pkt->transport_codes[0], &raw[i], 2); i += 2; - memcpy(&pkt->transport_codes[1], &raw[i], 2); i += 2; + if (tryParsePacket(pkt, raw, len)) { + pkt->_snr = _radio->getLastSNR() * 4.0f; + score = _radio->packetScore(_radio->getLastSNR(), len); + air_time = _radio->getEstAirtimeFor(len); + rx_air_time += air_time; } else { - pkt->transport_codes[0] = pkt->transport_codes[1] = 0; - } - pkt->path_len = raw[i++]; - - if (pkt->path_len > MAX_PATH_SIZE || i + pkt->path_len > len) { - MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", getLogDateTime(), len); _mgr->free(pkt); // put back into pool pkt = NULL; - } else { - memcpy(pkt->path, &raw[i], pkt->path_len); i += pkt->path_len; - - pkt->payload_len = len - i; // payload is remainder - if (pkt->payload_len > sizeof(pkt->payload)) { - MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): packet payload too big, payload_len=%d", getLogDateTime(), (uint32_t)pkt->payload_len); - _mgr->free(pkt); // put back into pool - pkt = NULL; - } else { - memcpy(pkt->payload, &raw[i], pkt->payload_len); - - pkt->_snr = _radio->getLastSNR() * 4.0f; - score = _radio->packetScore(_radio->getLastSNR(), len); - air_time = _radio->getEstAirtimeFor(len); - rx_air_time += air_time; - } } } } else { @@ -249,9 +260,6 @@ void Dispatcher::checkSend() { int len = 0; uint8_t raw[MAX_TRANS_UNIT]; -#ifdef NODE_ID - raw[len++] = NODE_ID; -#endif raw[len++] = outbound->header; if (outbound->hasTransportCodes()) { memcpy(&raw[len], &outbound->transport_codes[0], 2); len += 2; diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 25a41d82ce..0a448c4029 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -184,6 +184,7 @@ class Dispatcher { unsigned long futureMillis(int millis_from_now) const; private: + bool tryParsePacket(Packet* pkt, const uint8_t* raw, int len); void checkRecv(); void checkSend(); }; diff --git a/src/Identity.h b/src/Identity.h index c3ffcd75e3..008f7b5bf6 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -20,6 +20,10 @@ class Identity { memcpy(dest, pub_key, PATH_HASH_SIZE); // hash is just prefix of pub_key return PATH_HASH_SIZE; } + int copyHashTo(uint8_t* dest, uint8_t len) const { + memcpy(dest, pub_key, len); // hash is just prefix of pub_key + return len; + } bool isHashMatch(const uint8_t* hash) const { return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0; } diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 0548c9073d..57fee14036 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -39,11 +39,6 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int } DispatcherAction Mesh::onRecvPacket(Packet* pkt) { - if (pkt->getPayloadVer() > PAYLOAD_VER_1) { // not supported in this firmware version - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unsupported packet version", getLogDateTime()); - return ACTION_RELEASE; - } - if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { if (pkt->path_len < MAX_PATH_SIZE) { uint8_t i = 0; @@ -70,14 +65,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_CONTROL && (pkt->payload[0] & 0x80) != 0) { - if (pkt->path_len == 0) { + if (pkt->getPathHashCount() == 0) { onControlDataRecv(pkt); } // just zero-hop control packets allowed (for this subset of payloads) return ACTION_RELEASE; } - if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { + if (pkt->isRouteDirect() && pkt->getPathHashCount() > 0) { // check for 'early received' ACK if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { int i = 0; @@ -88,7 +83,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } } - if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { + if (self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) && allowPacketForward(pkt)) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { return forwardMultipartDirect(pkt); } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { @@ -158,7 +153,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) { int k = 0; uint8_t path_len = data[k++]; - uint8_t* path = &data[k]; k += path_len; + uint8_t hash_size = (path_len >> 6) + 1; + uint8_t hash_count = path_len & 63; + uint8_t* path = &data[k]; k += hash_size*hash_count; uint8_t extra_type = data[k++] & 0x0F; // upper 4 bits reserved for future use uint8_t* extra = &data[k]; uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!) @@ -293,8 +290,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK Packet tmp; tmp.header = pkt->header; - tmp.path_len = pkt->path_len; - memcpy(tmp.path, pkt->path, pkt->path_len); + tmp.path_len = Packet::copyPath(tmp.path, pkt->path, pkt->path_len); tmp.payload_len = pkt->payload_len - 1; memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); @@ -321,27 +317,25 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { void Mesh::removeSelfFromPath(Packet* pkt) { // remove our hash from 'path' - pkt->path_len -= PATH_HASH_SIZE; -#if 0 - memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); -#elif PATH_HASH_SIZE == 1 - for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1 - pkt->path[k] = pkt->path[k + 1]; + pkt->setPathHashCount(pkt->getPathHashCount() - 1); // decrement the count + + uint8_t sz = pkt->getPathHashSize(); + for (int k = 0; k < pkt->getPathHashCount()*sz; k += sz) { // shuffle path by 1 'entry' + memcpy(&pkt->path[k], &pkt->path[k + sz], sz); } -#else - #error "need path remove impl" -#endif } DispatcherAction Mesh::routeRecvPacket(Packet* packet) { + uint8_t n = packet->getPathHashCount(); if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit() - && packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) { + && (n + 1)*packet->getPathHashSize() <= MAX_PATH_SIZE && allowPacketForward(packet)) { // append this node's hash to 'path' - packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]); + self_id.copyHashTo(&packet->path[n * packet->getPathHashSize()], packet->getPathHashSize()); + packet->setPathHashCount(n + 1); uint32_t d = getRetransmitDelay(packet); // as this propagates outwards, give it lower and lower priority - return ACTION_RETRANSMIT_DELAYED(packet->path_len, d); // give priority to closer sources, than ones further away + return ACTION_RETRANSMIT_DELAYED(packet->getPathHashCount(), d); // give priority to closer sources, than ones further away } return ACTION_RELEASE; } @@ -353,8 +347,7 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) { if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK Packet tmp; tmp.header = pkt->header; - tmp.path_len = pkt->path_len; - memcpy(tmp.path, pkt->path, pkt->path_len); + tmp.path_len = Packet::copyPath(tmp.path, pkt->path, pkt->path_len); tmp.payload_len = pkt->payload_len - 1; memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); @@ -376,7 +369,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) { delay_millis += getDirectRetransmitDelay(packet) + 300; auto a1 = createMultiAck(crc, extra); if (a1) { - memcpy(a1->path, packet->path, a1->path_len = packet->path_len); + a1->path_len = Packet::copyPath(a1->path, packet->path, packet->path_len); a1->header &= ~PH_ROUTE_MASK; a1->header |= ROUTE_TYPE_DIRECT; sendPacket(a1, 0, delay_millis); @@ -386,7 +379,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) { auto a2 = createAck(crc); if (a2) { - memcpy(a2->path, packet->path, a2->path_len = packet->path_len); + a2->path_len = Packet::copyPath(a2->path, packet->path, packet->path_len); a2->header &= ~PH_ROUTE_MASK; a2->header |= ROUTE_TYPE_DIRECT; sendPacket(a2, 0, delay_millis); @@ -439,7 +432,10 @@ Packet* Mesh::createPathReturn(const Identity& dest, const uint8_t* secret, cons } Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len) { - if (path_len + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!! + uint8_t path_hash_size = (path_len >> 6) + 1; + uint8_t path_hash_count = path_len & 63; + + if (path_hash_count*path_hash_size + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!! Packet* packet = obtainNewPacket(); if (packet == NULL) { @@ -457,7 +453,7 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, uint8_t data[MAX_PACKET_PAYLOAD]; data[data_len++] = path_len; - memcpy(&data[data_len], path, path_len); data_len += path_len; + memcpy(&data[data_len], path, path_hash_count*path_hash_size); data_len += path_hash_count*path_hash_size; if (extra_len > 0) { data[data_len++] = extra_type; memcpy(&data[data_len], extra, extra_len); data_len += extra_len; @@ -624,15 +620,19 @@ Packet* Mesh::createControlData(const uint8_t* data, size_t len) { return packet; } -void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) { +void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_size) { if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); return; } + if (path_hash_size == 0 || path_hash_size > 3) { + MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): invalid path_hash_size", getLogDateTime()); + return; + } packet->header &= ~PH_ROUTE_MASK; packet->header |= ROUTE_TYPE_FLOOD; - packet->path_len = 0; + packet->setPathHashSizeAndCount(path_hash_size, 0); _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us @@ -647,17 +647,21 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) { sendPacket(packet, pri, delay_millis); } -void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) { +void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis, uint8_t path_hash_size) { if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); return; } + if (path_hash_size == 0 || path_hash_size > 3) { + MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): invalid path_hash_size", getLogDateTime()); + return; + } packet->header &= ~PH_ROUTE_MASK; packet->header |= ROUTE_TYPE_TRANSPORT_FLOOD; packet->transport_codes[0] = transport_codes[0]; packet->transport_codes[1] = transport_codes[1]; - packet->path_len = 0; + packet->setPathHashSizeAndCount(path_hash_size, 0); _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us @@ -679,13 +683,13 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin uint8_t pri; if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { // TRACE packets are different // for TRACE packets, path is appended to end of PAYLOAD. (path is used for SNR's) - memcpy(&packet->payload[packet->payload_len], path, path_len); + memcpy(&packet->payload[packet->payload_len], path, path_len); // NOTE: path_len here can be > 64, and NOT in the new scheme packet->payload_len += path_len; packet->path_len = 0; pri = 5; // maybe make this configurable } else { - memcpy(packet->path, path, packet->path_len = path_len); + packet->path_len = Packet::copyPath(packet->path, path, path_len); if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { pri = 1; // slightly less priority } else { diff --git a/src/Mesh.h b/src/Mesh.h index 00f7ed00f4..f9f8786320 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -196,13 +196,13 @@ class Mesh : public Dispatcher { /** * \brief send a locally-generated Packet with flood routing */ - void sendFlood(Packet* packet, uint32_t delay_millis=0); + void sendFlood(Packet* packet, uint32_t delay_millis=0, uint8_t path_hash_size=1); /** * \brief send a locally-generated Packet with flood routing * \param transport_codes array of 2 codes to attach to packet */ - void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); + void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0, uint8_t path_hash_size=1); /** * \brief send a locally-generated Packet with Direct routing diff --git a/src/Packet.cpp b/src/Packet.cpp index 2d54ca4590..0a75c2b3fa 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -10,8 +10,19 @@ Packet::Packet() { payload_len = 0; } +uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { + uint8_t hash_count = path_len & 63; + uint8_t hash_size = (path_len >> 6) + 1; + if (hash_count*hash_size > MAX_PATH_SIZE) { + MESH_DEBUG_PRINTLN("Packet::copyPath, invalid path_len=%d", (uint32_t)path_len); + return 0; // Error + } + memcpy(dest, src, hash_count*hash_size); + return path_len; +} + int Packet::getRawLength() const { - return 2 + path_len + payload_len + (hasTransportCodes() ? 4 : 0); + return 2 + getPathByteLen() + payload_len + (hasTransportCodes() ? 4 : 0); } void Packet::calculatePacketHash(uint8_t* hash) const { diff --git a/src/Packet.h b/src/Packet.h index 42d73f416c..b325fc1c9e 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -76,6 +76,14 @@ class Packet { */ uint8_t getPayloadVer() const { return (header >> PH_VER_SHIFT) & PH_VER_MASK; } + uint8_t getPathHashSize() const { return (path_len >> 6) + 1; } + uint8_t getPathHashCount() const { return path_len & 63; } + uint8_t getPathByteLen() const { return getPathHashCount() * getPathHashSize(); } + void setPathHashCount(uint8_t n) { path_len &= ~63; path_len |= n; } + void setPathHashSizeAndCount(uint8_t sz, uint8_t n) { path_len = ((sz - 1) << 6) | (n & 63); } + + static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len); + void markDoNotRetransmit() { header = 0xFF; } bool isMarkedDoNotRetransmit() const { return header == 0xFF; } diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 6de7469d0d..5ec678c7f4 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -39,7 +39,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl } void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { - if (dest.out_path_len < 0) { + if (dest.out_path_len == OUT_PATH_UNKNOWN) { mesh::Packet* ack = createAck(ack_hash); if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY); } else { @@ -92,7 +92,7 @@ ContactInfo* BaseChatMesh::allocateContactSlot() { void BaseChatMesh::populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp) { memset(&ci, 0, sizeof(ci)); ci.id = id; - ci.out_path_len = -1; // initially out_path is unknown + ci.out_path_len = OUT_PATH_UNKNOWN; StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); ci.type = parser.getType(); if (parser.hasLatLon()) { @@ -263,7 +263,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len); if (reply) { - if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT + if (from.out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY); } else { sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY); @@ -273,7 +273,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } } else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) { onContactResponse(from, data, len); - if (packet->isRouteFlood() && from.out_path_len >= 0) { + if (packet->isRouteFlood() && from.out_path_len != OUT_PATH_UNKNOWN) { // we have direct path, but other node is still sending flood response, so maybe they didn't receive reciprocal path properly(?) handleReturnPathRetry(from, packet->path, packet->path_len); } @@ -295,7 +295,7 @@ bool BaseChatMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const ui bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { // NOTE: default impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. // FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?) - memcpy(from.out_path, out_path, from.out_path_len = out_path_len); // store a copy of path, for sendDirect() + from.out_path_len = mesh::Packet::copyPath(from.out_path, out_path, out_path_len); // store a copy of path, for sendDirect() from.lastmod = getRTCClock()->getCurrentTime(); onContactPathUpdated(from); @@ -317,7 +317,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit - if (packet->isRouteFlood() && from->out_path_len >= 0) { + if (packet->isRouteFlood() && from->out_path_len != OUT_PATH_UNKNOWN) { // we have direct path, but other node is still sending flood, so maybe they didn't receive reciprocal path properly(?) handleReturnPathRetry(*from, packet->path, packet->path_len); } @@ -386,7 +386,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); int rc; - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; @@ -412,7 +412,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); int rc; - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; @@ -500,7 +500,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; @@ -525,7 +525,7 @@ int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; @@ -552,7 +552,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; @@ -579,7 +579,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; @@ -683,7 +683,7 @@ void BaseChatMesh::checkConnections() { MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact not found!"); continue; } - if (contact->out_path_len < 0) { + if (contact->out_path_len == OUT_PATH_UNKNOWN) { MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact, no out_path!"); continue; } @@ -710,7 +710,7 @@ void BaseChatMesh::checkConnections() { } void BaseChatMesh::resetPathTo(ContactInfo& recipient) { - recipient.out_path_len = -1; + recipient.out_path_len = OUT_PATH_UNKNOWN; } static ContactInfo* table; // pass via global :-( diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 55b70ca55c..1282382737 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -114,7 +114,7 @@ ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) { memset(c, 0, sizeof(*c)); c->permissions = init_perms; c->id = id; - c->out_path_len = -1; // initially out_path is unknown + c->out_path_len = OUT_PATH_UNKNOWN; return c; } diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index dfbc3fce1f..b758f7068d 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -10,10 +10,12 @@ #define PERM_ACL_READ_WRITE 2 #define PERM_ACL_ADMIN 3 +#define OUT_PATH_UNKNOWN 0xFF + struct ClientInfo { mesh::Identity id; uint8_t permissions; - int8_t out_path_len; + uint8_t out_path_len; uint8_t out_path[MAX_PATH_SIZE]; uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t last_timestamp; // by THEIR clock (transient) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index eff07741ab..ede977cace 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -3,12 +3,14 @@ #include #include +#define OUT_PATH_UNKNOWN 0xFF + struct ContactInfo { mesh::Identity id; char name[32]; uint8_t type; // on of ADV_TYPE_* uint8_t flags; - int8_t out_path_len; + uint8_t out_path_len; mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock From 3dc14976a03d46f855ce21faddc64866471aa702 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Sun, 22 Feb 2026 14:46:45 +0100 Subject: [PATCH 372/409] add companion usb build target for Heltec Wireless Tracker --- variants/heltec_tracker/platformio.ini | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 797eafdca4..dba05dcf5f 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -43,6 +43,31 @@ lib_deps = stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 +[env:Heltec_Wireless_Tracker_companion_radio_usb] +extends = Heltec_tracker_base +build_flags = + ${Heltec_tracker_base.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D DISPLAY_ROTATION=1 + -D DISPLAY_CLASS=ST7735Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D BLE_PIN_CODE=123456 ; HWT will use display for pin +; -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_base.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + + +lib_deps = + ${Heltec_tracker_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Heltec_Wireless_Tracker_companion_radio_ble] extends = Heltec_tracker_base build_flags = From 011edd3c999ca6c528016d67f3e68ac46830131c Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Sun, 22 Feb 2026 18:01:30 +0100 Subject: [PATCH 373/409] Fix millis() wraparound in PacketQueue time comparisons PacketQueue::countBefore() and PacketQueue::get() use unsigned comparison (_schedule_table[j] > now) to check if a packet is scheduled for the future. This breaks when millis() wraps around after ~49.7 days: packets scheduled just before the wrap appear to be in the far future and get stuck in the queue. Use signed subtraction instead, matching the approach already used by Dispatcher::millisHasNowPassed(). This correctly handles the wraparound for time differences up to ~24.8 days in either direction, well beyond the maximum queue delay of 32 seconds. --- src/helpers/StaticPoolPacketManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 125efb7582..67d6397930 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -11,7 +11,7 @@ PacketQueue::PacketQueue(int max_entries) { int PacketQueue::countBefore(uint32_t now) const { int n = 0; for (int j = 0; j < _num; j++) { - if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now + if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now n++; } return n; @@ -21,7 +21,7 @@ mesh::Packet* PacketQueue::get(uint32_t now) { uint8_t min_pri = 0xFF; int best_idx = -1; for (int j = 0; j < _num; j++) { - if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now + if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now if (_pri_table[j] < min_pri) { // select most important priority amongst non-future entries min_pri = _pri_table[j]; best_idx = j; From 5a885bffe4ad6e9e33771845a7e43e3a0db6d6d9 Mon Sep 17 00:00:00 2001 From: Sam Koucha <23366921+ElectroMW@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:14:39 +0000 Subject: [PATCH 374/409] Make full use of board's 8MB Flash and add companion WiFI target --- boards/t_beams3_supreme.json | 2 +- .../platformio.ini | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/boards/t_beams3_supreme.json b/boards/t_beams3_supreme.json index 6a72524731..3eb9c016e3 100644 --- a/boards/t_beams3_supreme.json +++ b/boards/t_beams3_supreme.json @@ -41,7 +41,7 @@ "name": "LilyGo T-Beam supreme (8MB Flash 8MB PSRAM)", "upload": { "flash_size": "8MB", - "maximum_ram_size": 327680, + "maximum_ram_size": 8388608, "maximum_size": 8388608, "require_upload_port": true, "speed": 460800 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 2d2a095aab..04e4b0bf01 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -26,7 +26,9 @@ build_src_filter = ${esp32_base.build_src_filter} + + + -board_build.partitions = min_spiffs.csv ; get around 4mb flash limit +board_build.partitions = default_8MB.csv +board_upload.flash_size = 8MB +board_upload.maximum_size = 8388608 lib_deps = ${esp32_base.lib_deps} lewisxhe/XPowersLib @ ^0.2.7 @@ -131,3 +133,27 @@ build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:T_Beam_S3_Supreme_SX1262_companion_radio_wifi] +extends = T_Beam_S3_Supreme_SX1262 +build_flags = + ${T_Beam_S3_Supreme_SX1262.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_SSID='"Three_CA7C65"' + -D WIFI_PWD='"8hC45a66265eA3w"' +; -D WIFI_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=8 +; -D MESH_DEBUG=1 +; -D ARDUHAL_LOG_LEVEL=4 +; -D CORE_DEBUG_LEVEL=4 +build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${T_Beam_S3_Supreme_SX1262.lib_deps} + densaugeo/base64 @ ~1.4.0 From a66773bac096252eb557aba1ed8ddafc955042fa Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 23 Feb 2026 14:25:19 +1100 Subject: [PATCH 375/409] * CommonCLI: added "get/set path.hash.mode " --- examples/companion_radio/MyMesh.cpp | 10 ++++------ examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 10 ++++++---- src/Packet.cpp | 19 ++++++++++++++++--- src/Packet.h | 4 +++- src/helpers/CommonCLI.cpp | 19 +++++++++++++++++-- src/helpers/CommonCLI.h | 1 + 8 files changed, 49 insertions(+), 18 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index f0f8e5fc92..26beeab9f5 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -678,7 +678,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, } } -bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t _in_path_len, uint8_t* out_path, uint8_t _out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { +bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 4) { uint32_t tag; memcpy(&tag, extra, 4); @@ -686,7 +686,7 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t _ if (tag == pending_discovery) { // check for matching response tag) pending_discovery = 0; - if (in_path_len > MAX_PATH_SIZE || out_path_len > MAX_PATH_SIZE) { + if (!mesh::Packet::isValidPathLen(in_path_len) || !mesh::Packet::isValidPathLen(out_path_len)) { MESH_DEBUG_PRINTLN("onContactPathRecv, invalid path sizes: %d, %d", in_path_len, out_path_len); } else { int i = 0; @@ -695,11 +695,9 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t _ memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix out_frame[i++] = out_path_len; - memcpy(&out_frame[i], out_path, out_path_len); - i += out_path_len; + i += mesh::Packet::writePath(&out_frame[i], out_path, out_path_len); out_frame[i++] = in_path_len; - memcpy(&out_frame[i], in_path, in_path_len); - i += in_path_len; + i += mesh::Packet::writePath(&out_frame[i], in_path, in_path_len); // NOTE: telemetry data in 'extra' is discarded at present _serial->writeFrame(out_frame, i); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 7328f7038f..81c1dcb425 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -917,7 +917,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); // TODO: which path_hash_size to use?? + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { sendZeroHop(pkt, delay_millis); } diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 0b1d3a1ba5..06d9cc960c 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -680,7 +680,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); // TODO: which path_hash_size to use? + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { sendZeroHop(pkt, delay_millis); } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index fc9ef3101d..68fea474ee 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -261,7 +261,8 @@ void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) { if (c->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(pkt, c->out_path, c->out_path_len); } else { - sendFlood(pkt); + unsigned long delay_millis = 0; + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } } t->send_expiry = futureMillis(ALERT_ACK_EXPIRY_MILLIS); @@ -567,7 +568,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); - if (path) sendFlood(path, TXT_ACK_DELAY); + if (path) sendFlood(path, TXT_ACK_DELAY, packet->getPathHashSize()); } else { sendAckTo(*from, ack_hash, packet->getPathHashSize()); } @@ -791,7 +792,7 @@ void SensorMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet* pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { sendZeroHop(pkt, delay_millis); } @@ -868,7 +869,8 @@ void SensorMesh::loop() { if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { mesh::Packet* pkt = createSelfAdvert(); - if (pkt) sendFlood(pkt); + unsigned long delay_millis = 0; + if (pkt) sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); updateFloodAdvertTimer(); // schedule next flood advert updateAdvertTimer(); // also schedule local advert (so they don't overlap) diff --git a/src/Packet.cpp b/src/Packet.cpp index 0a75c2b3fa..9fd063955a 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -10,14 +10,27 @@ Packet::Packet() { payload_len = 0; } -uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { +bool Packet::isValidPathLen(uint8_t path_len) { + uint8_t hash_count = path_len & 63; + uint8_t hash_size = (path_len >> 6) + 1; + if (hash_size == 4) return false; // Reserved for future + return hash_count*hash_size <= MAX_PATH_SIZE; +} + +size_t Packet::writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { uint8_t hash_count = path_len & 63; uint8_t hash_size = (path_len >> 6) + 1; - if (hash_count*hash_size > MAX_PATH_SIZE) { + size_t len = hash_count*hash_size; + if (len > MAX_PATH_SIZE) { MESH_DEBUG_PRINTLN("Packet::copyPath, invalid path_len=%d", (uint32_t)path_len); return 0; // Error } - memcpy(dest, src, hash_count*hash_size); + memcpy(dest, src, len); + return len; +} + +uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { + if (writePath(dest, src, path_len) == 0) return 0; return path_len; } diff --git a/src/Packet.h b/src/Packet.h index b325fc1c9e..7861954618 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -82,7 +82,9 @@ class Packet { void setPathHashCount(uint8_t n) { path_len &= ~63; path_len |= n; } void setPathHashSizeAndCount(uint8_t sz, uint8_t n) { path_len = ((sz - 1) << 6) | (n & 63); } - static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len); + static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len); // returns path_len + static size_t writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len); // returns byte length written + static bool isValidPathLen(uint8_t path_len); void markDoNotRetransmit() { header = 0xFF; } bool isMarkedDoNotRetransmit() const { return header == 0xFF; } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 263eb66522..f2f961b93a 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -63,7 +63,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 - file.read(pad, 3); // 121 + file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 + file.read(pad, 2); // 122 file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 @@ -95,6 +96,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, -9, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f); + _prefs->path_hash_mode = constrain(_prefs->path_hash_mode, 0, 2); // NOTE: mode 3 reserved for future // sanitise bad bridge pref values _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1); @@ -147,7 +149,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 - file.write(pad, 3); // 121 + file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 + file.write(pad, 2); // 122 file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 @@ -325,6 +328,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sp++; } *reply = 0; // set null terminator + } else if (memcmp(config, "path.hash.mode", 14) == 0) { + sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode); } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { @@ -556,6 +561,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch *dp = 0; savePrefs(); strcpy(reply, "OK"); + } else if (memcmp(config, "path.hash.mode ", 15) == 0) { + config += 15; + uint8_t mode = atoi(config); + if (mode < 3) { + _prefs->path_hash_mode = mode; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, must be 0,1, or 2"); + } } else if (memcmp(config, "tx ", 3) == 0) { _prefs->tx_power_dbm = atoi(&config[3]); savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 146e1c6e20..1e454ec290 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -52,6 +52,7 @@ struct NodePrefs { // persisted to file uint32_t discovery_mod_timestamp; float adc_multiplier; char owner_info[120]; + uint8_t path_hash_mode; // which path mode to use when sending }; class CommonCLICallbacks { From e52d57c06528a533d7734f886c8016489373e8cb Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 23 Feb 2026 18:26:56 +1100 Subject: [PATCH 376/409] * companion: new pref: path_hash_mode (0..2) * companion: new field in CMD_SET_OTHER_PARAMS, path_hash_mode * companion: CMD_SEND_SELF_ADVERT, cmd_frame[1] now holds the path hash size (0 = zero hop, 1..3 = flood path hash size) --- examples/companion_radio/DataStore.cpp | 6 ++++-- examples/companion_radio/MyMesh.cpp | 17 +++++++++++------ examples/companion_radio/NodePrefs.h | 1 + examples/simple_room_server/MyMesh.cpp | 3 ++- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 1239ea3d69..fba64e8c60 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -222,7 +222,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.read((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 - file.read(pad, 2); // 78 + file.read((uint8_t *)&_prefs.path_hash_mode, sizeof(_prefs.path_hash_mode)); // 78 + file.read(pad, 1); // 79 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 @@ -257,7 +258,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.write((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 - file.write(pad, 2); // 78 + file.write((uint8_t *)&_prefs.path_hash_mode, sizeof(_prefs.path_hash_mode)); // 78 + file.write(pad, 1); // 79 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 26beeab9f5..09968ecd82 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -473,23 +473,23 @@ bool MyMesh::allowPacketForward(const mesh::Packet* packet) { void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { // TODO: dynamic send_scope, depending on recipient and current 'home' Region if (send_scope.isNull()) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { uint16_t codes[2]; codes[0] = send_scope.calcTransportCode(pkt); codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region? - sendFlood(pkt, codes, delay_millis); + sendFlood(pkt, codes, delay_millis, _prefs.path_hash_mode + 1); } } void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) { // TODO: have per-channel send_scope if (send_scope.isNull()) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { uint16_t codes[2]; codes[0] = send_scope.calcTransportCode(pkt); codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region? - sendFlood(pkt, codes, delay_millis); + sendFlood(pkt, codes, delay_millis, _prefs.path_hash_mode + 1); } } @@ -937,6 +937,7 @@ void MyMesh::handleCmdFrame(size_t len) { StrHelper::strzcpy((char *)&out_frame[i], FIRMWARE_VERSION, 20); i += 20; out_frame[i++] = _prefs.client_repeat; // v9+ + out_frame[i++] = _prefs.path_hash_mode; // v10+ _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_APP_START && len >= 8) { // sent when app establishes connection, respond with node ID @@ -1113,8 +1114,9 @@ void MyMesh::handleCmdFrame(size_t len) { pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon); } if (pkt) { - if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop) - sendFlood(pkt); // TODO: which path_hash_size to use?? + if (len >= 2 && cmd_frame[1] >= 1 && cmd_frame[1] <= 3) { // optional param (1..3 = flood, 0 = zero hop) + unsigned long delay_millis = 0; + sendFlood(pkt, delay_millis, cmd_frame[1]); } else { sendZeroHop(pkt); } @@ -1306,6 +1308,9 @@ void MyMesh::handleCmdFrame(size_t len) { _prefs.advert_loc_policy = cmd_frame[3]; if (len >= 5) { _prefs.multi_acks = cmd_frame[4]; + if (len >= 6) { + _prefs.path_hash_mode = cmd_frame[5]; + } } } } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index f2a52f41e5..ec60c94ae0 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -29,4 +29,5 @@ struct NodePrefs { // persisted to file uint32_t gps_interval; // GPS read interval in seconds uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; + uint8_t path_hash_mode; // which path mode to use when sending }; \ No newline at end of file diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 06d9cc960c..3d2b57944e 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -74,7 +74,8 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len); if (reply) { if (client->out_path_len == OUT_PATH_UNKNOWN) { - sendFlood(reply); // TODO: which path_hash_size to use? + unsigned long delay_millis = 0; + sendFlood(reply, delay_millis, _prefs.path_hash_mode); client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); } else { sendDirect(reply, client->out_path, client->out_path_len); From 5b0884ad2d31ede8eae4f08591c69ad186109251 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 23 Feb 2026 21:08:22 +1100 Subject: [PATCH 377/409] * added CMD_SET_PATH_HASH_MODE --- examples/companion_radio/MyMesh.cpp | 12 +++++++++--- examples/companion_radio/MyMesh.h | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 09968ecd82..e928e7e0e4 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -57,6 +57,7 @@ #define CMD_SET_AUTOADD_CONFIG 58 #define CMD_GET_AUTOADD_CONFIG 59 #define CMD_GET_ALLOWED_REPEAT_FREQ 60 +#define CMD_SET_PATH_HASH_MODE 61 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -1308,14 +1309,19 @@ void MyMesh::handleCmdFrame(size_t len) { _prefs.advert_loc_policy = cmd_frame[3]; if (len >= 5) { _prefs.multi_acks = cmd_frame[4]; - if (len >= 6) { - _prefs.path_hash_mode = cmd_frame[5]; - } } } } savePrefs(); writeOKFrame(); + } else if (cmd_frame[0] == CMD_SET_PATH_HASH_MODE && cmd_frame[1] == 0 && len >= 3) { + if (cmd_frame[2] >= 3) { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); + } else { + _prefs.path_hash_mode = cmd_frame[2]; + savePrefs(); + writeOKFrame(); + } } else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) { if (dirty_contacts_expiry) { // is there are pending dirty contacts write needed? saveContacts(); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e3c109859e..87e6cf338c 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -5,7 +5,7 @@ #include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 9 +#define FIRMWARE_VER_CODE 10 #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "15 Feb 2026" From 45564bad9b6522a032e40d98cdcf9e83dacae70c Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 23 Feb 2026 23:51:30 +1100 Subject: [PATCH 378/409] * Dispatcher bug fixes --- src/Dispatcher.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 12889fb866..35eca0a911 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -68,7 +68,7 @@ void Dispatcher::loop() { next_tx_time = futureMillis(t * getAirtimeBudgetFactor()); _radio->onSendFinished(); - logTx(outbound, 2 + outbound->path_len + outbound->payload_len); + logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len); if (outbound->isRouteFlood()) { n_sent_flood++; } else { @@ -80,7 +80,7 @@ void Dispatcher::loop() { MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): WARNING: outbound packed send timed out!", getLogDateTime()); _radio->onSendFinished(); - logTxFail(outbound, 2 + outbound->path_len + outbound->payload_len); + logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len); releasePacket(outbound); // return to pool outbound = NULL; @@ -266,7 +266,7 @@ void Dispatcher::checkSend() { memcpy(&raw[len], &outbound->transport_codes[1], 2); len += 2; } raw[len++] = outbound->path_len; - memcpy(&raw[len], outbound->path, outbound->path_len); len += outbound->path_len; + len += Packet::writePath(&raw[len], outbound->path, outbound->path_len); if (len + outbound->payload_len > MAX_TRANS_UNIT) { MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): FATAL: Invalid packet queued... too long, len=%d", getLogDateTime(), len + outbound->payload_len); @@ -320,7 +320,7 @@ void Dispatcher::releasePacket(Packet* packet) { } void Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) { - if (packet->path_len > MAX_PATH_SIZE || packet->payload_len > MAX_PACKET_PAYLOAD) { + if (!Packet::isValidPathLen(packet->path_len) || packet->payload_len > MAX_PACKET_PAYLOAD) { MESH_DEBUG_PRINTLN("%s Dispatcher::sendPacket(): ERROR: invalid packet... path_len=%d, payload_len=%d", getLogDateTime(), (uint32_t) packet->path_len, (uint32_t) packet->payload_len); _mgr->free(packet); } else { From 213d085012ab59c24f462fdf80d64a7a20b9903f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 24 Feb 2026 00:08:13 +1100 Subject: [PATCH 379/409] * revert CMD_SEND_SELF_ADVERT, use _prefs.path_hash_mode --- examples/companion_radio/MyMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index e928e7e0e4..f23e50b640 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1115,9 +1115,9 @@ void MyMesh::handleCmdFrame(size_t len) { pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon); } if (pkt) { - if (len >= 2 && cmd_frame[1] >= 1 && cmd_frame[1] <= 3) { // optional param (1..3 = flood, 0 = zero hop) + if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop) unsigned long delay_millis = 0; - sendFlood(pkt, delay_millis, cmd_frame[1]); + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { sendZeroHop(pkt); } From 9d5c4865c346b949a9b172a344b4d80d8c97b074 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 24 Feb 2026 01:08:11 +1100 Subject: [PATCH 380/409] * room server fix --- examples/simple_room_server/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 3d2b57944e..5451505a29 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -75,7 +75,7 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { if (reply) { if (client->out_path_len == OUT_PATH_UNKNOWN) { unsigned long delay_millis = 0; - sendFlood(reply, delay_millis, _prefs.path_hash_mode); + sendFlood(reply, delay_millis, _prefs.path_hash_mode + 1); client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); } else { sendDirect(reply, client->out_path, client->out_path_len); From 9f4eeeecebc10851180d3d336445090d6b8d570c Mon Sep 17 00:00:00 2001 From: callum5892 Date: Mon, 23 Feb 2026 17:31:18 +0000 Subject: [PATCH 381/409] Added build flags for M5Stack Unit C6L Enabled USB-CDC on boot for M5Stack_Unit_C6L_companion_radio_usb to fix serial connection issues --- variants/m5stack_unit_c6l/platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini index a2b8b08736..1dd6749a15 100644 --- a/variants/m5stack_unit_c6l/platformio.ini +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -94,6 +94,8 @@ build_flags = ${M5Stack_Unit_C6L.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D ARDUINO_USB_MODE=1 build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + - From b14879ce2d1cfef2710e90ed35161a8d15aa5471 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 24 Feb 2026 14:23:59 +1100 Subject: [PATCH 382/409] * CMD_GET_ADVERT_PATH bug fix --- examples/companion_radio/MyMesh.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index f23e50b640..c96f7e0175 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -350,7 +350,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } // add inbound-path to mem cache - if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid + if (path && mesh::Packet::isValidPathLen(path_len)) { // check path is valid AdvertPath* p = advert_paths; uint32_t oldest = 0xFFFFFFFF; for (int i = 0; i < ADVERT_PATH_TABLE_SIZE; i++) { // check if already in table, otherwise evict oldest @@ -367,8 +367,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix)); strcpy(p->name, contact.name); p->recv_timestamp = getRTCClock()->getCurrentTime(); - p->path_len = path_len; - memcpy(p->path, path, p->path_len); + p->path_len = mesh::Packet::copyPath(p->path, path, path_len); } if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[] @@ -1696,11 +1695,12 @@ void MyMesh::handleCmdFrame(size_t len) { } } if (found) { - out_frame[0] = RESP_CODE_ADVERT_PATH; - memcpy(&out_frame[1], &found->recv_timestamp, 4); - out_frame[5] = found->path_len; - memcpy(&out_frame[6], found->path, found->path_len); - _serial->writeFrame(out_frame, 6 + found->path_len); + int i = 0; + out_frame[i++] = RESP_CODE_ADVERT_PATH; + memcpy(&out_frame[i], &found->recv_timestamp, 4); i += 4; + out_frame[i++] = found->path_len; + i += mesh::Packet::writePath(&out_frame[i], found->path, found->path_len); + _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_NOT_FOUND); } From b777a7c635e2c3ed329f82d45cf27919f949f7f7 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 24 Feb 2026 11:28:23 +0100 Subject: [PATCH 383/409] Update default preset to EU/UK (Narrow) --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index c47e757eee..ba601c26cd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,9 +24,9 @@ lib_deps = melopero/Melopero RV3028 @ ^1.1.0 electroniccats/CayenneLPP @ 1.6.1 build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 - -D LORA_FREQ=869.525 - -D LORA_BW=250 - -D LORA_SF=11 + -D LORA_FREQ=869.618 + -D LORA_BW=62.5 + -D LORA_SF=8 -D ENABLE_ADVERT_ON_BOOT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 ; NOTE: comment these out for more secure firmware -D ENABLE_PRIVATE_KEY_EXPORT=1 From f4748a7f9d4c5f1440c47c0bd52074f4e01e97b9 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 24 Feb 2026 21:30:04 +1100 Subject: [PATCH 384/409] * misc --- variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 04e4b0bf01..1ac622dbcc 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -142,8 +142,8 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 - -D WIFI_SSID='"Three_CA7C65"' - -D WIFI_PWD='"8hC45a66265eA3w"' + -D WIFI_SSID='"WIFI_SSID"' + -D WIFI_PWD='"Password"' ; -D WIFI_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=8 ; -D MESH_DEBUG=1 From 15cce12efd47c41fe67575a7c36d2e9b9ec4f1ba Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 25 Feb 2026 02:43:48 +0100 Subject: [PATCH 385/409] Add basic sanity test github PR workflow Build a few generic variants to verify at least those compile. Can't hurt. --- .github/workflows/pr-build-check.yml | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/pr-build-check.yml diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml new file mode 100644 index 0000000000..5ba677cd35 --- /dev/null +++ b/.github/workflows/pr-build-check.yml @@ -0,0 +1,43 @@ +name: PR Build Check + +on: + pull_request: + branches: [main, dev] + paths: + - 'src/**' + - 'examples/**' + - 'variants/**' + - 'platformio.ini' + - '.github/workflows/pr-build-check.yml' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + environment: + # ESP32-S3 (most common platform) + - Heltec_v3_companion_radio_ble + - Heltec_v3_repeater + - Heltec_v3_room_server + # nRF52 + - RAK_4631_companion_radio_ble + - RAK_4631_repeater + - RAK_4631_room_server + # RP2040 + - PicoW_repeater + # STM32 + - wio-e5-mini_repeater + # ESP32-C6 + - LilyGo_Tlora_C6_repeater_ + + steps: + - name: Clone Repo + uses: actions/checkout@v4 + + - name: Setup Build Environment + uses: ./.github/actions/setup-build-environment + + - name: Build ${{ matrix.environment }} + run: pio run -e ${{ matrix.environment }} From 8737c64fdb47d0813d8bd5b63b45ae9607dd9e45 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 25 Feb 2026 17:10:31 +1100 Subject: [PATCH 386/409] * Packet::copyPath() fix --- src/Packet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Packet.cpp b/src/Packet.cpp index 9fd063955a..934020b34b 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -30,7 +30,7 @@ size_t Packet::writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { } uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { - if (writePath(dest, src, path_len) == 0) return 0; + writePath(dest, src, path_len); return path_len; } From b67decfba06bbcafbb6909157b46a37c09106930 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 26 Feb 2026 15:36:21 +1100 Subject: [PATCH 387/409] * bug fix: Packet::writeTo(), Packet::readFrom() --- src/Packet.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Packet.cpp b/src/Packet.cpp index 934020b34b..aad3e2f48e 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -57,7 +57,7 @@ uint8_t Packet::writeTo(uint8_t dest[]) const { memcpy(&dest[i], &transport_codes[1], 2); i += 2; } dest[i++] = path_len; - memcpy(&dest[i], path, path_len); i += path_len; + i += writePath(&dest[i], path, path_len); memcpy(&dest[i], payload, payload_len); i += payload_len; return i; } @@ -72,8 +72,11 @@ bool Packet::readFrom(const uint8_t src[], uint8_t len) { transport_codes[0] = transport_codes[1] = 0; } path_len = src[i++]; - if (path_len > sizeof(path)) return false; // bad encoding - memcpy(path, &src[i], path_len); i += path_len; + if (!isValidPathLen(path_len)) return false; // bad encoding + + uint8_t bl = getPathByteLen(); + memcpy(path, &src[i], bl); i += bl; + if (i >= len) return false; // bad encoding payload_len = len - i; if (payload_len > sizeof(payload)) return false; // bad encoding From 8ad17d1022242ac066021778e5ca4b79c839225f Mon Sep 17 00:00:00 2001 From: enricolorenzoni59 Date: Sat, 28 Feb 2026 09:07:30 +0000 Subject: [PATCH 388/409] `gps sync` reply: fill buffer with text --- src/helpers/CommonCLI.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index f2f961b93a..e20bbb1c02 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -717,6 +717,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch LocationProvider * l = _sensors->getLocationProvider(); if (l != NULL) { l->syncTime(); + strcpy(reply, "ok"); + } else { + strcpy(reply, "gps provider not found"); } } else if (memcmp(command, "gps setloc", 10) == 0) { _prefs->node_lat = _sensors->node_lat; From 329e40819720981b2c208831ab38ac907a849970 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Fri, 6 Feb 2026 01:57:27 +0100 Subject: [PATCH 389/409] Hold GC1109 PA_POWER during deep sleep for LNA RX wake The GC1109 FEM needs its VFEM_Ctrl pin held HIGH during deep sleep to keep the LNA active, enabling proper RX sensitivity for wake-on-packet. Without this, the LNA is unpowered during sleep and RX wake sensitivity is degraded by ~17dB. Release RTC holds in begin() after configuring GPIO registers (not before) to ensure glitch-free pin transitions on wake. Trade-off: ~6.5mA additional sleep current for significantly improved wake-on-packet range. --- variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp | 11 +++++++++-- variants/heltec_tracker_v2/platformio.ini | 10 +++++----- variants/heltec_v4/HeltecV4Board.cpp | 11 +++++++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index 4975d5cdeb..1b694c11d4 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -6,12 +6,17 @@ void HeltecTrackerV2Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + // Set up digital GPIO registers before releasing RTC hold. The hold latches + // the pad state including function select, so register writes accumulate + // without affecting the pad. On hold release, all changes apply atomically + // (IO MUX switches to digital GPIO with output already HIGH — no glitch). pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); @@ -48,7 +53,9 @@ void HeltecTrackerV2Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + // Hold GC1109 FEM pins during sleep to keep LNA active for RX wake + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 25d16f2f66..af41b4f56a 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -17,11 +17,11 @@ build_flags = -D P_LORA_SCLK=9 -D P_LORA_MISO=11 -D P_LORA_MOSI=10 - -D P_LORA_PA_POWER=7 ;power en - -D P_LORA_PA_EN=4 - -D P_LORA_PA_TX_EN=46 ;enable tx - -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. - -D MAX_LORA_TX_POWER=22 ;Max SX1262 output + -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - GC1109 LDO power enable + -D P_LORA_PA_EN=4 ; CSD - GC1109 chip enable (HIGH=on) + -D P_LORA_PA_TX_EN=46 ; CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass) + -D LORA_TX_POWER=10 ; 10dBm + ~11dB GC1109 gain = ~21dBm output + -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -> ~28dBm at antenna -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 92f9343767..626f25773e 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -7,12 +7,17 @@ void HeltecV4Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + // Set up digital GPIO registers before releasing RTC hold. The hold latches + // the pad state including function select, so register writes accumulate + // without affecting the pad. On hold release, all changes apply atomically + // (IO MUX switches to digital GPIO with output already HIGH — no glitch). pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); @@ -50,7 +55,9 @@ void HeltecV4Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + // Hold GC1109 FEM pins during sleep to keep LNA active for RX wake + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet From 2bb6f636a4ae18da2f793501effe9265cd9654cd Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sun, 8 Feb 2026 16:36:13 +0100 Subject: [PATCH 390/409] Add 1ms delay after powering PA (cold-boot) --- variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp | 7 +++++-- variants/heltec_v4/HeltecV4Board.cpp | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index 1b694c11d4..bd7f680ea2 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -20,9 +20,12 @@ void HeltecTrackerV2Board::begin() { pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); - periph_power.begin(); - esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + delay(1); // GC1109 startup time after cold power-on + } + + periph_power.begin(); if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 626f25773e..8186f2d4b2 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -21,10 +21,12 @@ void HeltecV4Board::begin() { pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); + esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + delay(1); // GC1109 startup time after cold power-on + } periph_power.begin(); - - esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) From d9e67222f59391a4352bd406ed21bed69ae0dc22 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 25 Feb 2026 09:11:23 +0100 Subject: [PATCH 391/409] prefs is 5 char length :nerd: --- src/helpers/CommonCLI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e20bbb1c02..fd63127348 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -749,7 +749,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->advert_loc_policy = ADVERT_LOC_SHARE; savePrefs(); strcpy(reply, "ok"); - } else if (memcmp(command+11, "prefs", 4) == 0) { + } else if (memcmp(command+11, "prefs", 5) == 0) { _prefs->advert_loc_policy = ADVERT_LOC_PREFS; savePrefs(); strcpy(reply, "ok"); From 70f1ad4aebf22a70a663f310487bbb8eb167ddf8 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 25 Feb 2026 00:26:38 +0100 Subject: [PATCH 392/409] Fix RAK3401 SKY66122-11 FEM control: enable CSD/CPS for proper PA and LNA operation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RAK13302 1W module uses a Skyworks SKY66122-11 front-end module with three digital control pins (CSD, CTX, CPS) that must be actively driven by the host MCU. The previous code only managed CTX (GPIO 31) — toggling it for TX/RX — but never initialized CSD (GPIO 24) or CPS (GPIO 21), leaving them floating with no pull-up/pull-down resistors on the PCB. With floating CSD and CPS, the SKY66122 was in an undefined operating mode: - The 30 dB TX PA may not have been reliably engaging - The 16 dB RX LNA was never reliably active, degrading receive sensitivity --- variants/rak3401/RAK3401Board.cpp | 33 ++++++++++++++++++++++++++----- variants/rak3401/RAK3401Board.h | 9 +++++---- variants/rak3401/variant.h | 11 ++++++++--- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index b9431c929e..e2a9f318ca 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -23,10 +23,33 @@ void RAK3401Board::begin() { pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); -#ifdef P_LORA_PA_EN - // Initialize RAK13302 1W LoRa transceiver module PA control pin + // Initialize SKY66122-11 FEM on the RAK13302 module. + // CSD (P0.24) and CPS (P0.21) must be HIGH for both TX and RX modes. + // CTX (P0.31) selects TX(HIGH) vs RX(LOW) and also enables the 5V boost + // converter that powers the PA section (VCC1/VCC2). + // The LNA section (VSUP1/VCC0) runs on 3.3V and works with boost off. + pinMode(P_LORA_PA_CSD, OUTPUT); + digitalWrite(P_LORA_PA_CSD, HIGH); // CSD=1: enable FEM + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); // CPS=1: enable TX/RX paths + pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, LOW); // Start with PA disabled - delay(10); // Allow PA module to initialize + digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off + + delay(1); // SKY66122 turn-on settling time +} + +#ifdef NRF52_POWER_MANAGEMENT +void RAK3401Board::initiateShutdown(uint8_t reason) { + // Put SKY66122 in guaranteed <1 uA shutdown (Mode 4: CSD=0, CTX=0, CPS=0) + digitalWrite(P_LORA_PA_EN, LOW); // CTX=0, boost off + digitalWrite(SX126X_POWER_EN, LOW); // CPS=0 + digitalWrite(P_LORA_PA_CSD, LOW); // CSD=0 + + // Disable 3V3 switched peripherals + digitalWrite(PIN_3V3_EN, LOW); + + enterSystemOff(reason); +} #endif -} \ No newline at end of file diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 20edf9069e..8ca5b52eb2 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -38,13 +38,14 @@ class RAK3401Board : public NRF52BoardDCDC { return "RAK 3401"; } -#ifdef P_LORA_PA_EN + // SKY66122 FEM TX/RX switching via CTX pin. + // CTX=HIGH: TX mode + 5V boost ON (PA powered from VCC1/VCC2) + // CTX=LOW: RX mode + 5V boost OFF (LNA powered from VSUP1 at 3.3V) void onBeforeTransmit() override { - digitalWrite(P_LORA_PA_EN, HIGH); // Enable PA before transmission + digitalWrite(P_LORA_PA_EN, HIGH); // CTX=1: TX mode, boost on } void onAfterTransmit() override { - digitalWrite(P_LORA_PA_EN, LOW); // Disable PA after transmission to save power + digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off } -#endif }; diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index 56fe081694..f2ef4ace86 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -147,8 +147,14 @@ static const uint8_t AREF = PIN_AREF; #define SX126X_BUSY (9) #define SX126X_RESET (4) -#define SX126X_POWER_EN (21) -// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +// SKY66122-11 FEM control pins (active HIGH, active LOW = shutdown <1uA) +// CSD+CPS must be HIGH for TX and RX; CTX selects TX(HIGH) vs RX(LOW) +// CTX also enables the 5V boost converter for the PA during TX +#define P_LORA_PA_CSD (24) // P0.24 -> SKY66122 CSD (pin 11) - FEM enable +#define SX126X_POWER_EN (21) // P0.21 -> SKY66122 CPS (pin 1) - path select +#define P_LORA_PA_EN (31) // P0.31 -> SKY66122 CTX (pin 2) - TX/RX + boost EN + +// DIO2 has a NC 0R footprint (R25) to CTX; not connected by default #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 @@ -159,7 +165,6 @@ static const uint8_t AREF = PIN_AREF; #define P_LORA_DIO_1 SX126X_DIO1 #define P_LORA_BUSY SX126X_BUSY #define P_LORA_RESET SX126X_RESET -#define P_LORA_PA_EN 31 // enables 3.3V periphery like GPS or IO Module // Do not toggle this for GPS power savings From ac2aa03b0903200f81a0e6981d56e194a4ce8ae7 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 25 Feb 2026 01:18:16 +0100 Subject: [PATCH 393/409] Add SX126X_REGISTER_PATCH for RAK3401 --- variants/rak3401/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 7467ceb9c0..ecea031719 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -11,6 +11,7 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX with SKY66122 FEM build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak3401> + From 5a5568ed56ac25d4de6e10e9d2cde07d6bf686c1 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 26 Feb 2026 08:57:56 +0100 Subject: [PATCH 394/409] Drive CTX low first --- variants/rak3401/RAK3401Board.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index e2a9f318ca..4c18c6dde3 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -28,15 +28,18 @@ void RAK3401Board::begin() { // CTX (P0.31) selects TX(HIGH) vs RX(LOW) and also enables the 5V boost // converter that powers the PA section (VCC1/VCC2). // The LNA section (VSUP1/VCC0) runs on 3.3V and works with boost off. + // + // Drive CTX LOW first to prevent transient TX mode (Mode 2) while CSD/CPS + // are being enabled — the RAK13302 has no pull-downs on these pins. + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off + pinMode(P_LORA_PA_CSD, OUTPUT); digitalWrite(P_LORA_PA_CSD, HIGH); // CSD=1: enable FEM pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); // CPS=1: enable TX/RX paths - pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off - delay(1); // SKY66122 turn-on settling time } From 49d831350171f0ee377a781cd646382b7988ab86 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Fri, 27 Feb 2026 11:30:46 +0100 Subject: [PATCH 395/409] Fix pin mapping & TX switch (it's DIO2) --- variants/rak3401/RAK3401Board.cpp | 34 +++++++++++-------------------- variants/rak3401/RAK3401Board.h | 12 ++--------- variants/rak3401/variant.h | 17 ++++++++-------- 3 files changed, 23 insertions(+), 40 deletions(-) diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index 4c18c6dde3..33e1de4221 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -20,37 +20,27 @@ void RAK3401Board::begin() { Wire.begin(); + // PIN_3V3_EN (WB_IO2, P0.34) controls the 3V3_S switched peripheral rail + // AND the 5V boost regulator (U5) on the RAK13302 that powers the SKY66122 PA. + // Must stay HIGH during radio operation — do not toggle for power saving. pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); - // Initialize SKY66122-11 FEM on the RAK13302 module. - // CSD (P0.24) and CPS (P0.21) must be HIGH for both TX and RX modes. - // CTX (P0.31) selects TX(HIGH) vs RX(LOW) and also enables the 5V boost - // converter that powers the PA section (VCC1/VCC2). - // The LNA section (VSUP1/VCC0) runs on 3.3V and works with boost off. - // - // Drive CTX LOW first to prevent transient TX mode (Mode 2) while CSD/CPS - // are being enabled — the RAK13302 has no pull-downs on these pins. - pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off - - pinMode(P_LORA_PA_CSD, OUTPUT); - digitalWrite(P_LORA_PA_CSD, HIGH); // CSD=1: enable FEM - + // Enable SKY66122-11 FEM on the RAK13302 module. + // CSD and CPS are tied together on the RAK13302 PCB, routed to IO3 (P0.21). + // HIGH = FEM active (LNA for RX, PA path available for TX). + // TX/RX switching (CTX) is handled by SX1262 DIO2 via SetDIO2AsRfSwitchCtrl. pinMode(SX126X_POWER_EN, OUTPUT); - digitalWrite(SX126X_POWER_EN, HIGH); // CPS=1: enable TX/RX paths - - delay(1); // SKY66122 turn-on settling time + digitalWrite(SX126X_POWER_EN, HIGH); + delay(1); // SKY66122 turn-on settling time (tON = 3us typ) } #ifdef NRF52_POWER_MANAGEMENT void RAK3401Board::initiateShutdown(uint8_t reason) { - // Put SKY66122 in guaranteed <1 uA shutdown (Mode 4: CSD=0, CTX=0, CPS=0) - digitalWrite(P_LORA_PA_EN, LOW); // CTX=0, boost off - digitalWrite(SX126X_POWER_EN, LOW); // CPS=0 - digitalWrite(P_LORA_PA_CSD, LOW); // CSD=0 + // Disable SKY66122 FEM (CSD+CPS LOW = shutdown, <1 uA) + digitalWrite(SX126X_POWER_EN, LOW); - // Disable 3V3 switched peripherals + // Disable 3V3 switched peripherals and 5V boost digitalWrite(PIN_3V3_EN, LOW); enterSystemOff(reason); diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 8ca5b52eb2..3a080d5e2c 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -38,14 +38,6 @@ class RAK3401Board : public NRF52BoardDCDC { return "RAK 3401"; } - // SKY66122 FEM TX/RX switching via CTX pin. - // CTX=HIGH: TX mode + 5V boost ON (PA powered from VCC1/VCC2) - // CTX=LOW: RX mode + 5V boost OFF (LNA powered from VSUP1 at 3.3V) - void onBeforeTransmit() override { - digitalWrite(P_LORA_PA_EN, HIGH); // CTX=1: TX mode, boost on - } - - void onAfterTransmit() override { - digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off - } + // TX/RX switching is handled by SX1262 DIO2 -> SKY66122 CTX (hardware-timed). + // No onBeforeTransmit/onAfterTransmit overrides needed. }; diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index f2ef4ace86..268aec5380 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -147,14 +147,15 @@ static const uint8_t AREF = PIN_AREF; #define SX126X_BUSY (9) #define SX126X_RESET (4) -// SKY66122-11 FEM control pins (active HIGH, active LOW = shutdown <1uA) -// CSD+CPS must be HIGH for TX and RX; CTX selects TX(HIGH) vs RX(LOW) -// CTX also enables the 5V boost converter for the PA during TX -#define P_LORA_PA_CSD (24) // P0.24 -> SKY66122 CSD (pin 11) - FEM enable -#define SX126X_POWER_EN (21) // P0.21 -> SKY66122 CPS (pin 1) - path select -#define P_LORA_PA_EN (31) // P0.31 -> SKY66122 CTX (pin 2) - TX/RX + boost EN - -// DIO2 has a NC 0R footprint (R25) to CTX; not connected by default +// SKY66122-11 FEM control on the RAK13302 module: +// CSD + CPS are tied together on the PCB, routed to WisBlock IO3 (P0.21). +// Setting IO3 HIGH enables the FEM (LNA for RX, PA path for TX). +// CTX is connected to SX1262 DIO2 — the radio handles TX/RX switching +// in hardware via SetDIO2AsRfSwitchCtrl (microsecond-accurate, no GPIO needed). +// The 5V boost for the PA is enabled by WB_IO2 (P0.34 = PIN_3V3_EN). +#define SX126X_POWER_EN (21) // P0.21 = IO3 -> SKY66122 CSD+CPS (FEM enable) + +// CTX is driven by SX1262 DIO2, not a GPIO #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 From f81ec4b14ca7126e17d0005bf0c64adc8d87c821 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 19 Feb 2026 14:44:25 +0100 Subject: [PATCH 396/409] fix agc reset --- src/helpers/radiolib/RadioLibWrappers.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index cf3e1266b9..a4b4c3bae5 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -57,8 +57,11 @@ void RadioLibWrapper::resetAGC() { // make sure we're not mid-receive of packet! if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return; - // NOTE: according to higher powers, just issuing RadioLib's startReceive() will reset the AGC. - // revisit this if a better impl is discovered. + // Warm sleep powers down the entire analog frontend (including AGC), forcing a + // fresh gain calibration on the next startReceive(). A plain standby->startReceive + // cycle does NOT reset the AGC — the analog state can persist across STDBY_RC. + // The ~1-2 ms sleep gap is negligible vs the preamble budget (131 ms at SF11/BW250). + _radio->sleep(); state = STATE_IDLE; // trigger a startReceive() } From a2dc2eb50cda605be7ef619f358024162fb48009 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 19 Feb 2026 16:16:21 +0100 Subject: [PATCH 397/409] when doing AGC reset, call Calibrate(0x7F) 1. warm sleep 2. wake to stdby 3. Calibrate(0x7F) to reset all internal blocks 4. re-apply DIO2 RF / boosted gain & register patch to make sure everything is as it was --- src/helpers/radiolib/CustomLLCC68Wrapper.h | 26 +++++++++++++++++ src/helpers/radiolib/CustomSTM32WLxWrapper.h | 26 +++++++++++++++++ src/helpers/radiolib/CustomSX1262Wrapper.h | 30 ++++++++++++++++++++ src/helpers/radiolib/CustomSX1268Wrapper.h | 26 +++++++++++++++++ src/helpers/radiolib/RadioLibWrappers.cpp | 10 +++---- src/helpers/radiolib/RadioLibWrappers.h | 1 + 6 files changed, 114 insertions(+), 5 deletions(-) diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index f7dd7a9f16..826c8ed2da 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -19,4 +19,30 @@ class CustomLLCC68Wrapper : public RadioLibWrapper { int sf = ((CustomLLCC68 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { + auto* radio = (CustomLLCC68 *)_radio; + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif + } }; diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index 9e2d0441d9..ed65b18888 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -20,4 +20,30 @@ class CustomSTM32WLxWrapper : public RadioLibWrapper { int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { + auto* radio = (CustomSTM32WLx *)_radio; + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif + } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 1afee5e8bd..505b4996c9 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -22,4 +22,34 @@ class CustomSX1262Wrapper : public RadioLibWrapper { virtual void powerOff() override { ((CustomSX1262 *)_radio)->sleep(false); } + + void doResetAGC() override { + auto* radio = (CustomSX1262 *)_radio; + // Warm sleep powers down analog frontend (resets AGC gain state) + radio->sleep(true); + // Wake to STDBY_RC for calibration + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + // Recalibrate all blocks (ADC, PLL, image, oscillators) + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } + // Re-apply RX settings that calibration may reset +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif + } }; diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 5d7106b405..c87ee9770b 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -19,4 +19,30 @@ class CustomSX1268Wrapper : public RadioLibWrapper { int sf = ((CustomSX1268 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { + auto* radio = (CustomSX1268 *)_radio; + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index a4b4c3bae5..53a4b0a2a0 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -53,15 +53,15 @@ void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) { } } +void RadioLibWrapper::doResetAGC() { + _radio->sleep(); // warm sleep to reset analog frontend +} + void RadioLibWrapper::resetAGC() { // make sure we're not mid-receive of packet! if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return; - // Warm sleep powers down the entire analog frontend (including AGC), forcing a - // fresh gain calibration on the next startReceive(). A plain standby->startReceive - // cycle does NOT reset the AGC — the analog state can persist across STDBY_RC. - // The ~1-2 ms sleep gap is negligible vs the preamble budget (131 ms at SF11/BW250). - _radio->sleep(); + doResetAGC(); state = STATE_IDLE; // trigger a startReceive() } diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 9ac1bbaeb3..b338b03a48 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -16,6 +16,7 @@ class RadioLibWrapper : public mesh::Radio { void startRecv(); float packetScoreInt(float snr, int sf, int packet_len); virtual bool isReceivingPacket() =0; + virtual void doResetAGC(); public: RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } From 9106ab46e1a05103c244ab6dd981d5cfdd3fbccd Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 19 Feb 2026 16:52:57 +0100 Subject: [PATCH 398/409] reset noise_floor sampling after agc reset --- src/helpers/radiolib/RadioLibWrappers.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 53a4b0a2a0..2216ca8f39 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -63,6 +63,14 @@ void RadioLibWrapper::resetAGC() { doResetAGC(); state = STATE_IDLE; // trigger a startReceive() + + // Reset noise floor sampling so it reconverges from scratch. + // Without this, a stuck _noise_floor of -120 makes the sampling threshold + // too low (-106) to accept normal samples (~-105), self-reinforcing the + // stuck value even after the receiver has recovered. + _noise_floor = 0; + _num_floor_samples = 0; + _floor_sample_sum = 0; } void RadioLibWrapper::loop() { From b2032e11b66048ffd45b7d0280255df5bb58783e Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 19 Feb 2026 17:34:48 +0100 Subject: [PATCH 399/409] make it more dry --- src/helpers/radiolib/CustomLLCC68Wrapper.h | 27 ++-------------- src/helpers/radiolib/CustomSTM32WLxWrapper.h | 27 ++-------------- src/helpers/radiolib/CustomSX1262Wrapper.h | 31 ++---------------- src/helpers/radiolib/CustomSX1268Wrapper.h | 27 ++-------------- src/helpers/radiolib/SX126xReset.h | 33 ++++++++++++++++++++ 5 files changed, 41 insertions(+), 104 deletions(-) create mode 100644 src/helpers/radiolib/SX126xReset.h diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index 826c8ed2da..9e783a955b 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -2,6 +2,7 @@ #include "CustomLLCC68.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" class CustomLLCC68Wrapper : public RadioLibWrapper { public: @@ -20,29 +21,5 @@ class CustomLLCC68Wrapper : public RadioLibWrapper { return packetScoreInt(snr, sf, packet_len); } - void doResetAGC() override { - auto* radio = (CustomLLCC68 *)_radio; - radio->sleep(true); - radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); - uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; - radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); - radio->mod->hal->delay(5); - uint32_t start = millis(); - while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { - if (millis() - start > 50) break; - radio->mod->hal->yield(); - } -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif -#ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif -#ifdef SX126X_REGISTER_PATCH - uint8_t r_data = 0; - radio->readRegister(0x8B5, &r_data, 1); - r_data |= 0x01; - radio->writeRegister(0x8B5, &r_data, 1); -#endif - } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index ed65b18888..e3e5202949 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -2,6 +2,7 @@ #include "CustomSTM32WLx.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" #include class CustomSTM32WLxWrapper : public RadioLibWrapper { @@ -21,29 +22,5 @@ class CustomSTM32WLxWrapper : public RadioLibWrapper { return packetScoreInt(snr, sf, packet_len); } - void doResetAGC() override { - auto* radio = (CustomSTM32WLx *)_radio; - radio->sleep(true); - radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); - uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; - radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); - radio->mod->hal->delay(5); - uint32_t start = millis(); - while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { - if (millis() - start > 50) break; - radio->mod->hal->yield(); - } -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif -#ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif -#ifdef SX126X_REGISTER_PATCH - uint8_t r_data = 0; - radio->readRegister(0x8B5, &r_data, 1); - r_data |= 0x01; - radio->writeRegister(0x8B5, &r_data, 1); -#endif - } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 505b4996c9..5856720b9e 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -2,6 +2,7 @@ #include "CustomSX1262.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" class CustomSX1262Wrapper : public RadioLibWrapper { public: @@ -23,33 +24,5 @@ class CustomSX1262Wrapper : public RadioLibWrapper { ((CustomSX1262 *)_radio)->sleep(false); } - void doResetAGC() override { - auto* radio = (CustomSX1262 *)_radio; - // Warm sleep powers down analog frontend (resets AGC gain state) - radio->sleep(true); - // Wake to STDBY_RC for calibration - radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); - // Recalibrate all blocks (ADC, PLL, image, oscillators) - uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; - radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); - radio->mod->hal->delay(5); - uint32_t start = millis(); - while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { - if (millis() - start > 50) break; - radio->mod->hal->yield(); - } - // Re-apply RX settings that calibration may reset -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif -#ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif -#ifdef SX126X_REGISTER_PATCH - uint8_t r_data = 0; - radio->readRegister(0x8B5, &r_data, 1); - r_data |= 0x01; - radio->writeRegister(0x8B5, &r_data, 1); -#endif - } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index c87ee9770b..5149fc431f 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -2,6 +2,7 @@ #include "CustomSX1268.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" class CustomSX1268Wrapper : public RadioLibWrapper { public: @@ -20,29 +21,5 @@ class CustomSX1268Wrapper : public RadioLibWrapper { return packetScoreInt(snr, sf, packet_len); } - void doResetAGC() override { - auto* radio = (CustomSX1268 *)_radio; - radio->sleep(true); - radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); - uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; - radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); - radio->mod->hal->delay(5); - uint32_t start = millis(); - while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { - if (millis() - start > 50) break; - radio->mod->hal->yield(); - } -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif -#ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif -#ifdef SX126X_REGISTER_PATCH - uint8_t r_data = 0; - radio->readRegister(0x8B5, &r_data, 1); - r_data |= 0x01; - radio->writeRegister(0x8B5, &r_data, 1); -#endif - } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/SX126xReset.h b/src/helpers/radiolib/SX126xReset.h new file mode 100644 index 0000000000..ba08ef8dbe --- /dev/null +++ b/src/helpers/radiolib/SX126xReset.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +// Full receiver reset for all SX126x-family chips (SX1262, SX1268, LLCC68, STM32WLx). +// Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration, +// then re-applies RX settings that calibration may reset. +inline void sx126xResetAGC(SX126x* radio) { + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } + +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif +} From f54948e06db394c74269b064e0d92dfc193c0f64 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 21 Feb 2026 15:33:38 +0100 Subject: [PATCH 400/409] Also implement LR11x10 AGC reset Similar to SX126x but simpler. --- src/helpers/radiolib/CustomLR1110Wrapper.h | 4 +++- src/helpers/radiolib/LR11x0Reset.h | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/helpers/radiolib/LR11x0Reset.h diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index 947bb51dd4..be4a6cde65 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -2,11 +2,13 @@ #include "CustomLR1110.h" #include "RadioLibWrappers.h" +#include "LR11x0Reset.h" class CustomLR1110Wrapper : public RadioLibWrapper { public: CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceivingPacket() override { + void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio); } + bool isReceivingPacket() override { return ((CustomLR1110 *)_radio)->isReceiving(); } float getCurrentRSSI() override { diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h new file mode 100644 index 0000000000..539ed44e83 --- /dev/null +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +// Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121). +// Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks, +// then re-applies RX settings that calibration may reset. +inline void lr11x0ResetAGC(LR11x0* radio) { + radio->sleep(true, 0); + radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true); + + radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL); + +#ifdef RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif +} From 85f764a114c299644587d40cb2ec08a15f4cf4d1 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 21 Feb 2026 17:34:28 +0100 Subject: [PATCH 401/409] Calibrate configured frequency for AGC reset --- src/helpers/radiolib/CustomLR1110.h | 2 ++ src/helpers/radiolib/CustomLR1110Wrapper.h | 2 +- src/helpers/radiolib/LR11x0Reset.h | 6 +++++- src/helpers/radiolib/SX126xReset.h | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index e4332013ad..b1f68080b5 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -20,6 +20,8 @@ class CustomLR1110 : public LR1110 { return len; } + float getFreqMHz() const { return freqMHz; } + bool isReceiving() { uint16_t irq = getIrqStatus(); bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index be4a6cde65..a1e0a49370 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -7,7 +7,7 @@ class CustomLR1110Wrapper : public RadioLibWrapper { public: CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio); } + void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); } bool isReceivingPacket() override { return ((CustomLR1110 *)_radio)->isReceiving(); } diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h index 539ed44e83..47cca627a2 100644 --- a/src/helpers/radiolib/LR11x0Reset.h +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -5,12 +5,16 @@ // Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121). // Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks, // then re-applies RX settings that calibration may reset. -inline void lr11x0ResetAGC(LR11x0* radio) { +inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { radio->sleep(true, 0); radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true); radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL); + // calibrate(0x3F) defaults image calibration to an unknown band. + // Re-calibrate for the actual operating frequency (band=4MHz matches RadioLib default). + radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f); + #ifdef RX_BOOSTED_GAIN radio->setRxBoostedGainMode(RX_BOOSTED_GAIN); #endif diff --git a/src/helpers/radiolib/SX126xReset.h b/src/helpers/radiolib/SX126xReset.h index ba08ef8dbe..39ddb73eed 100644 --- a/src/helpers/radiolib/SX126xReset.h +++ b/src/helpers/radiolib/SX126xReset.h @@ -18,6 +18,10 @@ inline void sx126xResetAGC(SX126x* radio) { radio->mod->hal->yield(); } + // Calibrate(0x7F) defaults image calibration to 902-928MHz band. + // Re-calibrate for the actual operating frequency. + radio->calibrateImage(radio->freqMHz); + #ifdef SX126X_DIO2_AS_RF_SWITCH radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); #endif From 9bae9d0ed2a6de07847ec3e4ae47c5346028ab9e Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 21 Feb 2026 17:42:33 +0100 Subject: [PATCH 402/409] fix comment, we know the band now after checking LR1110 user manual --- src/helpers/radiolib/LR11x0Reset.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h index 47cca627a2..d06ffc538e 100644 --- a/src/helpers/radiolib/LR11x0Reset.h +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -11,7 +11,7 @@ inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL); - // calibrate(0x3F) defaults image calibration to an unknown band. + // calibrate(0x3F) defaults image calibration to 902-928MHz band. // Re-calibrate for the actual operating frequency (band=4MHz matches RadioLib default). radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f); From 59d9770ab97dce5cf47a142e7ff38b2bb686d0ac Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Fri, 9 Jan 2026 05:06:17 +0100 Subject: [PATCH 403/409] Add GPS support Heltec Wireless Tracker v1.x Pin mapping verified against HTIT-Tracker V0.5 schematic: - GPIO35 (GPS_EN): N-ch MOSFET drives P-ch high-side switch, active HIGH - GPIO36 (GPS_RST): hardware reset, active LOW - GPIO33/34: UART TX/RX Delegates power management to MicroNMEALocationProvider begin()/stop() which independently controls GPS power via GPS_EN and shares VEXT with the display through RefCountedDigitalPin. --- variants/heltec_tracker/platformio.ini | 5 +++++ variants/heltec_tracker/target.cpp | 11 +++++------ variants/heltec_tracker/target.h | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index dba05dcf5f..1dbda126d2 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -32,6 +32,11 @@ build_flags = -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) -D PIN_GPS_RX=33 -D PIN_GPS_TX=34 + -D PIN_GPS_EN=35 ; N-ch MOSFET Q2 drives P-ch high-side switch → active HIGH (default) + -D PIN_GPS_RESET=36 + -D PIN_GPS_RESET_ACTIVE=LOW + -D GPS_BAUD_RATE=115200 + -D ENV_INCLUDE_GPS=1 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 25c2634bb1..f801bacb94 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -16,7 +16,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +// GPS_EN (GPIO35) drives N-ch MOSFET → P-ch high-side switch; GPS_RESET (GPIO36) active LOW +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); HWTSensorManager sensors = HWTSensorManager(nmea); #ifdef DISPLAY_CLASS @@ -58,18 +59,16 @@ mesh::LocalIdentity radio_new_identity() { void HWTSensorManager::start_gps() { if (!gps_active) { - board.periph_power.claim(); - + _location->begin(); // Claims periph_power via RefCountedDigitalPin gps_active = true; - Serial1.println("$CFGSYS,h35155*68"); + Serial1.println("$CFGSYS,h35155*68"); // Configure GPS for all constellations } } void HWTSensorManager::stop_gps() { if (gps_active) { gps_active = false; - - board.periph_power.release(); + _location->stop(); // Releases periph_power via RefCountedDigitalPin } } diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 5296fb2c24..29099f46a3 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -28,6 +28,7 @@ class HWTSensorManager : public SensorManager { const char* getSettingName(int i) const override; const char* getSettingValue(int i) const override; bool setSettingValue(const char* name, const char* value) override; + LocationProvider* getLocationProvider() override { return _location; } }; extern HeltecV3Board board; From 8a9a0dca5f6c1cc554d3b7c001b38d5937bb281d Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Mon, 9 Feb 2026 10:56:17 +0100 Subject: [PATCH 404/409] Fix GPS +8mA power leak when disabled (nRF52) On the T114, GPS_RESET (pin 38) is the same pin as PIN_3V3_EN. MicroNMEALocationProvider::begin() sets pin 38 HIGH (powering the 3V3 rail) but stop() never set it back LOW, leaving the GPS module powered even when disabled. Assert reset pin in stop() to mirror begin(), and guard _location->loop() behind gps_active check. Fixes meshcore-dev/MeshCore#1628 --- src/helpers/sensors/EnvironmentSensorManager.cpp | 4 +++- src/helpers/sensors/MicroNMEALocationProvider.h | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index a75d378c86..f7b08508bf 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -707,7 +707,9 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS - _location->loop(); + if (gps_active) { + _location->loop(); + } if (millis() > next_gps_update) { if(gps_active){ diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 574570a35f..1de7532758 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -79,7 +79,10 @@ public : if (_pin_en != -1) { digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE); } - if (_peripher_power) _peripher_power->release(); + if (_pin_reset != -1) { + digitalWrite(_pin_reset, GPS_RESET_FORCE); + } + if (_peripher_power) _peripher_power->release(); } bool isEnabled() override { From 00566741f65fd90ab3f563cb198fcc142b909b59 Mon Sep 17 00:00:00 2001 From: Wouter Bijen Date: Mon, 2 Mar 2026 20:41:41 +0100 Subject: [PATCH 405/409] Add configurable max hops filter for auto-add contacts Filter auto-add of new contacts by hop count (issues #1533, #1546). Setting is configurable from the companion app via extended CMD_SET/GET_AUTOADD_CONFIG protocol (0 = no limit, 1-63 = max hops). Co-Authored-By: Claude Opus 4.6 --- examples/companion_radio/DataStore.cpp | 2 ++ examples/companion_radio/MyMesh.cpp | 10 +++++++++- examples/companion_radio/MyMesh.h | 1 + examples/companion_radio/NodePrefs.h | 1 + src/helpers/BaseChatMesh.cpp | 9 +++++++++ src/helpers/BaseChatMesh.h | 1 + 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index fba64e8c60..d9ebacb418 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -229,6 +229,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 + file.read((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88 file.close(); } @@ -265,6 +266,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 + file.write((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c96f7e0175..7477ce8e9c 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -318,6 +318,10 @@ bool MyMesh::shouldOverwriteWhenFull() const { return (_prefs.autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) != 0; } +uint8_t MyMesh::getAutoAddMaxHops() const { + return _prefs.autoadd_max_hops; +} + void MyMesh::onContactOverwrite(const uint8_t* pub_key) { _store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); // delete from storage if (_serial->isConnected()) { @@ -1785,12 +1789,16 @@ void MyMesh::handleCmdFrame(size_t len) { } } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { _prefs.autoadd_config = cmd_frame[1]; + if (len >= 3) { + _prefs.autoadd_max_hops = cmd_frame[2]; + } savePrefs(); - writeOKFrame(); + writeOKFrame(); } else if (cmd_frame[0] == CMD_GET_AUTOADD_CONFIG) { int i = 0; out_frame[i++] = RESP_CODE_AUTOADD_CONFIG; out_frame[i++] = _prefs.autoadd_config; + out_frame[i++] = _prefs.autoadd_max_hops; _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_ALLOWED_REPEAT_FREQ) { int i = 0; diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 87e6cf338c..fe2c19bf23 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -119,6 +119,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { bool isAutoAddEnabled() const override; bool shouldAutoAddContactType(uint8_t type) const override; bool shouldOverwriteWhenFull() const override; + uint8_t getAutoAddMaxHops() const override; void onContactsFull() override; void onContactOverwrite(const uint8_t* pub_key) override; bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index ec60c94ae0..3fd96660a3 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -30,4 +30,5 @@ struct NodePrefs { // persisted to file uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending + uint8_t autoadd_max_hops; // 0 = no limit, 1-63 = max hops for auto-add }; \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 5ec678c7f4..279e361c9a 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -141,6 +141,15 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } + // check hop limit for new contacts (0 = no limit) + uint8_t max_hops = getAutoAddMaxHops(); + if (max_hops > 0 && packet->getPathHashCount() > max_hops) { + ContactInfo ci; + populateContactFromAdvert(ci, id, parser, timestamp); + onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know + return; + } + from = allocateContactSlot(); if (from == NULL) { ContactInfo ci; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index fd391b9808..ad14cc1f28 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -98,6 +98,7 @@ class BaseChatMesh : public mesh::Mesh { virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } virtual void onContactsFull() {}; virtual bool shouldOverwriteWhenFull() const { return false; } + virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1-63 = max hops for auto-add virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; From c016db86d5e18d1cbd4a90e1b1391b532d90feea Mon Sep 17 00:00:00 2001 From: Wouter Bijen Date: Tue, 3 Mar 2026 08:37:22 +0100 Subject: [PATCH 406/409] Address PR review: subtract-1 encoding and clamp max_hops - Change > to >= so stored value 1 means direct/0-hop only (liamcottle) - Clamp max_hops to 63 on write since getPathHashCount() caps at 63 (robekl) - Update comments to reflect encoding: 0=no limit, 1=direct only, N=up to N-1 hops Co-Authored-By: Claude Opus 4.6 --- examples/companion_radio/MyMesh.cpp | 2 +- examples/companion_radio/NodePrefs.h | 2 +- src/helpers/BaseChatMesh.cpp | 4 ++-- src/helpers/BaseChatMesh.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7477ce8e9c..6ec24ab11c 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1790,7 +1790,7 @@ void MyMesh::handleCmdFrame(size_t len) { } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { _prefs.autoadd_config = cmd_frame[1]; if (len >= 3) { - _prefs.autoadd_max_hops = cmd_frame[2]; + _prefs.autoadd_max_hops = min(cmd_frame[2], (uint8_t)63); } savePrefs(); writeOKFrame(); diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 3fd96660a3..0a59a6dc7c 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -30,5 +30,5 @@ struct NodePrefs { // persisted to file uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending - uint8_t autoadd_max_hops; // 0 = no limit, 1-63 = max hops for auto-add + uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct only, N = up to N-1 hops (max 63) }; \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 279e361c9a..84c6ae4a3e 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -141,9 +141,9 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } - // check hop limit for new contacts (0 = no limit) + // check hop limit for new contacts (0 = no limit, 1 = direct only, N = up to N-1 hops) uint8_t max_hops = getAutoAddMaxHops(); - if (max_hops > 0 && packet->getPathHashCount() > max_hops) { + if (max_hops > 0 && packet->getPathHashCount() >= max_hops) { ContactInfo ci; populateContactFromAdvert(ci, id, parser, timestamp); onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index ad14cc1f28..0dd8873949 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -98,7 +98,7 @@ class BaseChatMesh : public mesh::Mesh { virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } virtual void onContactsFull() {}; virtual bool shouldOverwriteWhenFull() const { return false; } - virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1-63 = max hops for auto-add + virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1 = direct only, N = up to N-1 hops virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; From 2cb08775c010bd1474a0b57d942812965406beda Mon Sep 17 00:00:00 2001 From: Wouter Bijen Date: Tue, 3 Mar 2026 08:40:17 +0100 Subject: [PATCH 407/409] Clarify comment wording: 1 = direct (0 hops) Co-Authored-By: Claude Opus 4.6 --- examples/companion_radio/NodePrefs.h | 2 +- src/helpers/BaseChatMesh.cpp | 2 +- src/helpers/BaseChatMesh.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 0a59a6dc7c..0c887802cb 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -30,5 +30,5 @@ struct NodePrefs { // persisted to file uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending - uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct only, N = up to N-1 hops (max 63) + uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 63) }; \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 84c6ae4a3e..33d7edbee4 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -141,7 +141,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } - // check hop limit for new contacts (0 = no limit, 1 = direct only, N = up to N-1 hops) + // check hop limit for new contacts (0 = no limit, 1 = direct (0 hops), N = up to N-1 hops) uint8_t max_hops = getAutoAddMaxHops(); if (max_hops > 0 && packet->getPathHashCount() >= max_hops) { ContactInfo ci; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 0dd8873949..ab90d581be 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -98,7 +98,7 @@ class BaseChatMesh : public mesh::Mesh { virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } virtual void onContactsFull() {}; virtual bool shouldOverwriteWhenFull() const { return false; } - virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1 = direct only, N = up to N-1 hops + virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; From 1d190ad9440d54b01afee1d2c763c43a2459170c Mon Sep 17 00:00:00 2001 From: Wouter Bijen Date: Tue, 3 Mar 2026 09:05:53 +0100 Subject: [PATCH 408/409] Clamp max_hops to 64 to cover full protocol hop range (0-63) --- examples/companion_radio/MyMesh.cpp | 2 +- examples/companion_radio/NodePrefs.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6ec24ab11c..1f71a9bc6c 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1790,7 +1790,7 @@ void MyMesh::handleCmdFrame(size_t len) { } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { _prefs.autoadd_config = cmd_frame[1]; if (len >= 3) { - _prefs.autoadd_max_hops = min(cmd_frame[2], (uint8_t)63); + _prefs.autoadd_max_hops = min(cmd_frame[2], (uint8_t)64); } savePrefs(); writeOKFrame(); diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 0c887802cb..090209c1dc 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -30,5 +30,5 @@ struct NodePrefs { // persisted to file uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending - uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 63) + uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) }; \ No newline at end of file From d076ba2f3b9b20d15ddae9ef7737ab5ffebe16ab Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 4 Mar 2026 03:51:07 +0100 Subject: [PATCH 409/409] fix user button wakeup --- src/helpers/MeshadventurerBoard.h | 7 +++---- src/helpers/esp32/TBeamBoard.h | 7 +++---- variants/heltec_e213/HeltecE213Board.cpp | 13 +++++++++---- variants/heltec_e290/HeltecE290Board.cpp | 13 +++++++++---- variants/heltec_t190/HeltecT190Board.cpp | 13 +++++++++---- variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp | 13 +++++++++---- variants/heltec_v2/HeltecV2Board.h | 7 +++---- variants/heltec_v4/HeltecV4Board.cpp | 13 +++++++++---- variants/lilygo_tdeck/TDeckBoard.h | 7 +++---- variants/rak3112/RAK3112Board.h | 7 +++---- variants/station_g2/StationG2Board.h | 7 +++---- 11 files changed, 63 insertions(+), 44 deletions(-) diff --git a/src/helpers/MeshadventurerBoard.h b/src/helpers/MeshadventurerBoard.h index 65e1110294..aa1e3276d7 100644 --- a/src/helpers/MeshadventurerBoard.h +++ b/src/helpers/MeshadventurerBoard.h @@ -44,10 +44,9 @@ class MeshadventurerBoard : public ESP32Board { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { diff --git a/src/helpers/esp32/TBeamBoard.h b/src/helpers/esp32/TBeamBoard.h index 4ff9555103..f9d5d55c82 100644 --- a/src/helpers/esp32/TBeamBoard.h +++ b/src/helpers/esp32/TBeamBoard.h @@ -140,10 +140,9 @@ bool power_init(); rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { diff --git a/variants/heltec_e213/HeltecE213Board.cpp b/variants/heltec_e213/HeltecE213Board.cpp index af11531821..8aa4a791bd 100644 --- a/variants/heltec_e213/HeltecE213Board.cpp +++ b/variants/heltec_e213/HeltecE213Board.cpp @@ -29,10 +29,9 @@ void HeltecE213Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { @@ -44,7 +43,13 @@ void HeltecE213Board::begin() { } void HeltecE213Board::powerOff() { +#ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW) { delay(10); } + delay(50); + enterDeepSleep(0, PIN_USER_BTN); +#else enterDeepSleep(0); +#endif } uint16_t HeltecE213Board::getBattMilliVolts() { diff --git a/variants/heltec_e290/HeltecE290Board.cpp b/variants/heltec_e290/HeltecE290Board.cpp index 3994a20616..af16f0be2b 100644 --- a/variants/heltec_e290/HeltecE290Board.cpp +++ b/variants/heltec_e290/HeltecE290Board.cpp @@ -29,10 +29,9 @@ void HeltecE290Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { @@ -44,7 +43,13 @@ void HeltecE290Board::begin() { } void HeltecE290Board::powerOff() { +#ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW) { delay(10); } + delay(50); + enterDeepSleep(0, PIN_USER_BTN); +#else enterDeepSleep(0); +#endif } uint16_t HeltecE290Board::getBattMilliVolts() { diff --git a/variants/heltec_t190/HeltecT190Board.cpp b/variants/heltec_t190/HeltecT190Board.cpp index 4f35be400b..223360ccd3 100644 --- a/variants/heltec_t190/HeltecT190Board.cpp +++ b/variants/heltec_t190/HeltecT190Board.cpp @@ -29,10 +29,9 @@ void HeltecT190Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { @@ -44,7 +43,13 @@ void HeltecT190Board::begin() { } void HeltecT190Board::powerOff() { +#ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW) { delay(10); } + delay(50); + enterDeepSleep(0, PIN_USER_BTN); +#else enterDeepSleep(0); +#endif } uint16_t HeltecT190Board::getBattMilliVolts() { diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index bd7f680ea2..e37d0b357b 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -60,10 +60,9 @@ void HeltecTrackerV2Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { @@ -75,7 +74,13 @@ void HeltecTrackerV2Board::begin() { } void HeltecTrackerV2Board::powerOff() { +#ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW) { delay(10); } + delay(50); + enterDeepSleep(0, PIN_USER_BTN); +#else enterDeepSleep(0); +#endif } uint16_t HeltecTrackerV2Board::getBattMilliVolts() { diff --git a/variants/heltec_v2/HeltecV2Board.h b/variants/heltec_v2/HeltecV2Board.h index a6221036dd..619dc13e77 100644 --- a/variants/heltec_v2/HeltecV2Board.h +++ b/variants/heltec_v2/HeltecV2Board.h @@ -35,10 +35,9 @@ class HeltecV2Board : public ESP32Board { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 8186f2d4b2..2087a07ba4 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -61,10 +61,9 @@ void HeltecV4Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { @@ -76,7 +75,13 @@ void HeltecV4Board::begin() { } void HeltecV4Board::powerOff() { +#ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW) { delay(10); } + delay(50); + enterDeepSleep(0, PIN_USER_BTN); +#else enterDeepSleep(0); +#endif } uint16_t HeltecV4Board::getBattMilliVolts() { diff --git a/variants/lilygo_tdeck/TDeckBoard.h b/variants/lilygo_tdeck/TDeckBoard.h index 7ed007af9c..076c6c2559 100644 --- a/variants/lilygo_tdeck/TDeckBoard.h +++ b/variants/lilygo_tdeck/TDeckBoard.h @@ -32,10 +32,9 @@ class TDeckBoard : public ESP32Board { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { diff --git a/variants/rak3112/RAK3112Board.h b/variants/rak3112/RAK3112Board.h index 8ba3197cf6..c1426cdb47 100644 --- a/variants/rak3112/RAK3112Board.h +++ b/variants/rak3112/RAK3112Board.h @@ -60,10 +60,9 @@ class RAK3112Board : public ESP32Board { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) { diff --git a/variants/station_g2/StationG2Board.h b/variants/station_g2/StationG2Board.h index a905682c8d..30fe8199db 100644 --- a/variants/station_g2/StationG2Board.h +++ b/variants/station_g2/StationG2Board.h @@ -30,10 +30,9 @@ class StationG2Board : public ESP32Board { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - if (pin_wake_btn < 0) { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet - } else { - esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + if (pin_wake_btn >= 0) { + esp_sleep_enable_ext0_wakeup((gpio_num_t)pin_wake_btn, LOW); // wake up on: button press (LOW) } if (secs > 0) {