From 45ab0e8cf7656ceff4e30041cece1a656a8f00d5 Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 5 Oct 2025 13:58:25 +0200 Subject: [PATCH 01/61] 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 00000000..a53cbc62 --- /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 00000000..81d0239f --- /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 00000000..6a7e3177 --- /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 00000000..064f3ae8 --- /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 00000000..efdaac61 --- /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 00000000..bb78e923 --- /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 c4a2b139308ecd3a03028fb2083a159311624b2e Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 11 Oct 2025 21:52:48 +0200 Subject: [PATCH 02/61] 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 c63ed2d8..afdaf639 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 @@ public: // 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 @@ public: 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 b43c0870..dcf566b3 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 b2125664..739aecfe 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 03/61] 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 5bf78778..a6221036 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 @@ public: 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 100053ed..539cc52e 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 2e5b17de..48d750be 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 04/61] 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 2e31571f..a905682c 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 016a46a9..bda8b7ae 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 5423af68..c2ee97e2 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 3f67af3a..2bf7016d 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 05/61] 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 c595740f..545219b2 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 @@ public: 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 e580959c..9ef9734e 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 380d733b..326a0dee 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 006af527768f2aebffe3f12cb6dc4fb60cfd6fc7 Mon Sep 17 00:00:00 2001 From: haxwithaxe Date: Fri, 17 Oct 2025 14:20:55 -0400 Subject: [PATCH 06/61] 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 a871b9ab..732dac43 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 07/61] 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 4bb9a2bb..374384bd 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 c1a39a11..a9ba7998 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 92ea1889..8f8a11fe 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 87f20f5a..b8bb698a 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 ce567016..ea59aa92 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 6bb3bdb5..555b182f 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 7d62a2783652c096c9038edd985ff92431091149 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 18 Oct 2025 13:37:18 +0200 Subject: [PATCH 08/61] 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 b6484a00..7c75a089 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 e7b73040..64866de0 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 09/61] 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 81d0239f..ad7212ec 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 @@ protected: 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 6a7e3177..aabedd24 100644 --- a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h +++ b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h @@ -124,6 +124,5 @@ public: class SCIndicatorDisplay : public LGFXDisplay { LGFX disp; public: - SCIndicatorDisplay() : LGFXDisplay(480, 480) - { display=&disp; } + SCIndicatorDisplay() : LGFXDisplay(480, 480, disp) {} }; From a421215e848d7ce6e2afc9559bdeeae3fd7a2435 Mon Sep 17 00:00:00 2001 From: recrof Date: Sat, 18 Oct 2025 23:42:28 +0200 Subject: [PATCH 10/61] 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 d4600d00..1200f599 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 11/61] 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 00000000..8d820fce --- /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 a8807e36..9a0504dc 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 00000000..bd85e971 --- /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 00000000..e83d1339 --- /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 a55cec19..37593f61 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 12/61] 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 00000000..160926b2 --- /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 00000000..3a2f067e --- /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 00000000..8011fae6 --- /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 00000000..a5dee363 --- /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 00000000..0238947f --- /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 00000000..cb3c1624 --- /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 00000000..b05def8a --- /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 00000000..928f56ec --- /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 13/61] 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 3b40e7cf..f8d170b5 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 7a7a654b..49d1ec37 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -27,9 +27,6 @@ public: 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 @@ public: 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 14/61] 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 00000000..ca3638b3 --- /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 00000000..823acbc7 --- /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 00000000..313430ff --- /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 00000000..cc302a85 --- /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 00000000..9bd4a22b --- /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 00000000..9691a304 --- /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 00000000..cc72c328 --- /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 87677fda76dc5c83cbc6feac8f74d041fab2597e Mon Sep 17 00:00:00 2001 From: recrof Date: Wed, 22 Oct 2025 15:15:29 +0200 Subject: [PATCH 15/61] 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 3772fc1b..02f1a21d 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 b8bb698a..32ef632d 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 ac1513129627e505544264b8ff46282a4a46f3c1 Mon Sep 17 00:00:00 2001 From: Wesley Ellis Date: Wed, 22 Oct 2025 16:17:06 -0400 Subject: [PATCH 16/61] 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 1200f599..56a51719 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 bb70c0b5..41d50e92 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 133d2650..5f1c08e2 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -21,6 +21,7 @@ protected: 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 17/61] 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 41d50e92..98339105 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 18/61] 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 37593f61..081cb0d0 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 19/61] 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 11814a22..916bb464 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 20/61] 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 374384bd..f328c752 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 c45c141d..a9ab251e 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 21/61] 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 a9ba7998..5d245ba1 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 60ef1e73..88e30bf2 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 22/61] * 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 53fd5e02..c482a30a 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 d2fa6c4c..5b786437 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 1876aadc..6306cd69 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -30,6 +30,7 @@ public: 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 23/61] 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 f723bb7f..cdf3cc7c 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 24/61] 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 cdf3cc7c..2e536de5 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 d4eb04d6e96ad2e776c5e1d7ec00a5efb6c67239 Mon Sep 17 00:00:00 2001 From: WattleFoxxo Date: Wed, 29 Oct 2025 15:20:31 +1100 Subject: [PATCH 25/61] 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 a801aae8..b7c19975 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 26/61] 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 f328c752..abd284ff 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 a9ab251e..694e8ff9 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 @@ public: 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 5d245ba1..592861bf 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 88e30bf2..d149b37b 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 @@ public: 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 8f8a11fe..f914a6b6 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 cdc3940c..ba55bc70 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 @@ public: 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 b8bb698a..eac2698e 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 ea59aa92..3cfca46c 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -66,6 +66,9 @@ public: 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 00000000..d0107f3b --- /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 27/61] 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 74598a2d..41824e1c 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 3289c975..ccc81b4e 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 72a40dec..b27e7f2b 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 28/61] 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 41824e1c..93bdc000 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 ccc81b4e..2039b526 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 b27e7f2b..01e9dfc9 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 29/61] 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 b5bce215..b1dd2367 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 308c85eb..88a811b5 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 6e963432..9c971083 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 30/61] 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 2039b526..201b3631 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 31/61] * 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 abd284ff..9a974565 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 592861bf..cc657800 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 32/61] * 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 9a974565..9c9471f5 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 cc657800..a6ccb140 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 f914a6b6..6564c4ef 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 33/61] * 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 89adca59..82c8c21d 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 7db0e7d4..5843df74 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 8f6b6d58..1a3b4d6e 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 eac35898..da1bac5b 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -548,7 +548,7 @@ public: 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 2dacd1b4..a5fcc148 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 5c7e1760..94bf351d 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -72,6 +72,11 @@ public: */ 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 a736c9b0..97596daa 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 02eedf52..11364cd8 100644 --- a/src/helpers/AutoDiscoverRTCClock.h +++ b/src/helpers/AutoDiscoverRTCClock.h @@ -14,4 +14,8 @@ public: 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 96e786fa9e17b1f35406830aa812c60aa812f283 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 30 Oct 2025 19:11:04 +1100 Subject: [PATCH 34/61] * 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 9c9471f5..5e62ab6d 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 a6ccb140..4d953c9c 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 35/61] 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 313430ff..ec65e706 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 cc302a85..c659d708 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 36/61] 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 f1adb05e..4441d291 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 37/61] 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 4441d291..eac027b8 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 38/61] * 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 b055d811..a480a9c3 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 39/61] * 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 5e62ab6d..1afad18b 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 a480a9c3..53dc74f5 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 a8fdb2a4..cbf1c9cf 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -196,6 +196,12 @@ public: */ 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 e03dd088..9b1eb1ce 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 40/61] 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 7c75a089..086f8259 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 c24d33a4..32d5f3a0 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 d0caa3be0447bc3eecc3d2a301c56021ba6a3734 Mon Sep 17 00:00:00 2001 From: Devin Carraway Date: Fri, 31 Oct 2025 22:11:24 -0700 Subject: [PATCH 41/61] 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 732dac43..f2127941 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 42/61] * 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 1afad18b..21f5a76b 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 694e8ff9..7e34ef5f 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 @@ protected: } #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 00000000..4c270ff8 --- /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 00000000..4620a745 --- /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 00000000..973ad703 --- /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 00000000..f25ed53f --- /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 43/61] * 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 4c270ff8..db9ea2d5 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 4620a745..69c5220b 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -33,7 +33,11 @@ public: 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 973ad703..4f689a12 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 f25ed53f..fc9d2532 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 44/61] * 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 | 128 ++++++++++++++++++++++++++-- src/helpers/RegionMap.h | 22 +++-- 5 files changed, 219 insertions(+), 22 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 21f5a76b..2cd81ef6 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 7e34ef5f..9ab93747 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 5843df74..7387e77e 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 db9ea2d5..7b6456b4 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -2,25 +2,106 @@ #include #include -void RegionMap::load(FILESYSTEM* _fs) { - // TODO -} -void RegionMap::save(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, "(*)"); } -RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id) { +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, 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 69c5220b..3858bfbd 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -24,20 +24,24 @@ class RegionMap { 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); + void printChildRegions(int indent, const RegionEntry* parent, Stream& out) const; - RegionEntry* putRegion(const char* name, uint16_t parent_id); +public: + 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 45/61] * 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 02f1a21d..b8ddbfa7 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 e6400871..bfb8bb18 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -68,6 +68,7 @@ #endif #include +#include /* -------------------------------------------------------------------------------------- */ @@ -107,6 +108,9 @@ protected: 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 @@ private: 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 53dc74f5..1ee6ce5c 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 cbf1c9cf..e96043e8 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -186,6 +186,12 @@ public: */ 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 9b1eb1ce..b4072657 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 9392001e..76b0dd1c 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -107,6 +107,9 @@ protected: 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 4f689a12..8b7c891b 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 fc9d2532..e3ba1524 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 5c80334dbda5a09fd44104a70168fe79a1bf0fea Mon Sep 17 00:00:00 2001 From: David Hall Date: Mon, 3 Nov 2025 21:00:43 +0000 Subject: [PATCH 46/61] 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 00000000..7ae06a35 --- /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 ed8584ff..f7de9244 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 f184c757..b84ab42b 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 47/61] 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 f7de9244..41f25da6 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 9ebeb477aa3313123323a967c8981a6ab96ea82f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Wed, 5 Nov 2025 14:34:44 +1100 Subject: [PATCH 48/61] * 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 b8ddbfa7..3c882d18 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 bfb8bb18..fb0fb479 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -107,6 +107,7 @@ protected: 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 2cd81ef6..dce0f18b 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 9ab93747..83331541 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -149,7 +149,7 @@ protected: } #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 1ee6ce5c..71b8eaee 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 e96043e8..70fa80f3 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -43,6 +43,11 @@ protected: */ 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 7b6456b4..074084ec 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 3858bfbd..5ad9df29 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 49/61] * 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 3c882d18..904cd871 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 50/61] * 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 dce0f18b..0bfb7c89 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 51/61] * 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 0bfb7c89..74fca86c 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 83331541..fd0b4d6a 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -157,6 +157,7 @@ protected: 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 71b8eaee..f1271574 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 70fa80f3..bc1ac54d 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -133,6 +133,11 @@ protected: */ 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 @@ public: 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 e52ab526..42d73f41 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 88327aa8..caed368b 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 3cfca46c..a665e014 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 52/61] * 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 74fca86c..2fb01029 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 fd0b4d6a..45001597 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 bc1ac54d..00f7ed00 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -44,6 +44,7 @@ protected: 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 53/61] * 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 2fb01029..7489d611 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 074084ec..c6221db0 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 5ad9df29..c3f897c5 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 @@ public: 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 54/61] * 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 7489d611..9d313d21 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 45001597..86fac3f4 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 00000000..a6633c0a --- /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 55/61] * 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 9d313d21..a996cbf6 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 6564c4ef..58bce766 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 56/61] * 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 a996cbf6..b06ae42a 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 c6221db0..7d1c08e6 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 c3f897c5..50513be1 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 57/61] * 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 904cd871..16be3e9c 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 fb0fb479..927f22e4 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -133,6 +133,7 @@ protected: 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 58/61] * 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 16be3e9c..598d535f 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 927f22e4..f2b56e5e 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 963290ea159c00edf4283bafd71464b27c8d95dc Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 7 Nov 2025 14:42:06 +1100 Subject: [PATCH 59/61] * 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 b06ae42a..cae05b20 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 7d1c08e6..36844615 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 8b7c891b..f34610b6 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 0044fd28..224eb873 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 3154766c..89789990 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -12,4 +12,5 @@ public: static void strncpy(char* dest, const char* src, size_t buf_sz); static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls static const char* ftoa(float f); + static 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 60/61] * 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 aa260855..92c410be 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 4d00f930..f66088f7 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 61/61] * 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 f66088f7..5a41e69c 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 cae05b20..622b73a6 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 }