From a38418e09a450d9564db82b614285406c45c83eb Mon Sep 17 00:00:00 2001 From: Matthias Wientapper Date: Wed, 22 Oct 2025 20:01:15 +0200 Subject: [PATCH 01/42] * Add display of IP address to companion screen --- examples/companion_radio/ui-new/UITask.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a089..38c09781 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2,6 +2,8 @@ #include #include "../MyMesh.h" #include "target.h" +#include + #ifndef AUTO_OFF_MILLIS #define AUTO_OFF_MILLIS 15000 // 15 seconds @@ -129,6 +131,8 @@ class HomeScreen : public UIScreen { bool sensors_scroll = false; int sensors_scroll_offset = 0; int next_sensors_refresh = 0; + + char ipStr[20]; void refresh_sensors() { if (millis() > next_sensors_refresh) { @@ -192,10 +196,17 @@ public: sprintf(tmp, "MSG: %d", _task->getMsgCount()); display.drawTextCentered(display.width() / 2, 20, tmp); + #ifdef WIFI_SSID + IPAddress ip = WiFi.localIP(); + snprintf(ipStr, sizeof(ipStr), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 54, ipStr); + #endif if (_task->hasConnection()) { display.setColor(DisplayDriver::GREEN); display.setTextSize(1); display.drawTextCentered(display.width() / 2, 43, "< Connected >"); + } else if (the_mesh.getBLEPin() != 0) { // BT pin display.setColor(DisplayDriver::RED); display.setTextSize(2); From f339c74bb489e2394583f427642c21dbae3b5625 Mon Sep 17 00:00:00 2001 From: Matthias Wientapper Date: Mon, 27 Oct 2025 17:58:29 +0100 Subject: [PATCH 02/42] * Add #ifdef, reuse variable --- examples/companion_radio/ui-new/UITask.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 38c09781..35218ac2 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2,8 +2,9 @@ #include #include "../MyMesh.h" #include "target.h" -#include - +#ifdef WIFI_SSID + #include +#endif #ifndef AUTO_OFF_MILLIS #define AUTO_OFF_MILLIS 15000 // 15 seconds @@ -132,8 +133,6 @@ class HomeScreen : public UIScreen { int sensors_scroll_offset = 0; int next_sensors_refresh = 0; - char ipStr[20]; - void refresh_sensors() { if (millis() > next_sensors_refresh) { sensors_lpp.reset(); @@ -198,9 +197,9 @@ public: #ifdef WIFI_SSID IPAddress ip = WiFi.localIP(); - snprintf(ipStr, sizeof(ipStr), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); display.setTextSize(1); - display.drawTextCentered(display.width() / 2, 54, ipStr); + display.drawTextCentered(display.width() / 2, 54, tmp); #endif if (_task->hasConnection()) { display.setColor(DisplayDriver::GREEN); From ff4fa7be31d5668126ce84aa770da420042f71b0 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Fri, 31 Oct 2025 14:42:16 +0100 Subject: [PATCH 03/42] Add ESP32-S3-Zero board configuration and Nibble Screen Connect variant --- boards/esp32-s3-zero.json | 40 +++++ variants/nibble_screen_connect/platformio.ini | 159 ++++++++++++++++++ variants/nibble_screen_connect/target.cpp | 49 ++++++ variants/nibble_screen_connect/target.h | 30 ++++ 4 files changed, 278 insertions(+) create mode 100644 boards/esp32-s3-zero.json create mode 100644 variants/nibble_screen_connect/platformio.ini create mode 100644 variants/nibble_screen_connect/target.cpp create mode 100644 variants/nibble_screen_connect/target.h diff --git a/boards/esp32-s3-zero.json b/boards/esp32-s3-zero.json new file mode 100644 index 00000000..7a9dbc53 --- /dev/null +++ b/boards/esp32-s3-zero.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "-D ARDUINO_USB_CDC_ON_BOOT=1", + "-D ARDUINO_USB_MSC_ON_BOOT=0", + "-D ARDUINO_USB_DFU_ON_BOOT=0", + "-D ARDUINO_USB_MODE=1", + "-D ARDUINO_RUNNING_CORE=1", + "-D ARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "ESP32-S3-Zero", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.espressif.com", + "vendor": "Espressif" +} + diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini new file mode 100644 index 00000000..e18bcde1 --- /dev/null +++ b/variants/nibble_screen_connect/platformio.ini @@ -0,0 +1,159 @@ +[nibble_screen_connect_base] +extends = esp32_base +board = esp32-s3-zero +build_flags = + ${esp32_base.build_flags} + -I variants/nibble_screen_connect + -D NIBBLE_SCREEN_CONNECT + -D P_LORA_DIO_1=4 + -D P_LORA_NSS=10 + -D P_LORA_RESET=6 + -D P_LORA_BUSY=5 + -D P_LORA_SCLK=13 + -D P_LORA_MISO=12 + -D P_LORA_MOSI=11 + -D PIN_USER_BTN=1 + -D PIN_BOARD_SDA=8 + -D PIN_BOARD_SCL=7 + -D HAS_NEOPIXEL + -D NEOPIXEL_COUNT=1 + -D NEOPIXEL_DATA=21 + -D NEOPIXEL_TYPE=(NEO_GRB+NEO_KHZ800) + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/nibble_screen_connect> +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + adafruit/Adafruit NeoPixel @ ^1.12.3 + +[env:nibble_screen_connect_repeater] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Nibble Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_repeater_bridge_espnow] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_terminal_chat] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=1 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_room_server] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Nibble Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_companion_radio_usb] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_companion_radio_ble] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_companion_radio_wifi] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + diff --git a/variants/nibble_screen_connect/target.cpp b/variants/nibble_screen_connect/target.cpp new file mode 100644 index 00000000..1980e039 --- /dev/null +++ b/variants/nibble_screen_connect/target.cpp @@ -0,0 +1,49 @@ +#include +#include "target.h" + +ESP32Board board; + +static SPIClass spi; +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + return radio.std_init(&spi); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} + diff --git a/variants/nibble_screen_connect/target.h b/variants/nibble_screen_connect/target.h new file mode 100644 index 00000000..66e69901 --- /dev/null +++ b/variants/nibble_screen_connect/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ESP32Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + From 397d280c3bdf524134726f644111108a3bc1f5f9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:25:31 +0100 Subject: [PATCH 04/42] stop OLED powering on every message if connected to phone --- examples/companion_radio/ui-new/UITask.cpp | 9 ++++++++- examples/companion_radio/ui-orig/UITask.cpp | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a089..886823ff 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -596,9 +596,16 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i setCurrScreen(msg_preview); if (_display != NULL) { - if (!_display->isOn()) _display->turnOn(); + // Only turn on display if it's off AND not connected to phone via BLE + // If connected to phone, user will see messages there, so don't wake the OLED + if (!_display->isOn() && !hasConnection()) { + _display->turnOn(); + } + // Always extend auto-off timer and trigger refresh if display is on + if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _next_refresh = 100; // trigger refresh + } } } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d..20d45bec 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -136,9 +136,16 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i StrHelper::strncpy(_msg, text, sizeof(_msg)); if (_display != NULL) { - if (!_display->isOn()) _display->turnOn(); + // Only turn on display if it's off AND not connected to phone via BLE + // If connected to phone, user will see messages there, so don't wake the OLED + if (!_display->isOn() && !hasConnection()) { + _display->turnOn(); + } + // Always extend auto-off timer and trigger refresh if display is on + if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _need_refresh = true; + } } } From eae16cfc5f0690cb20bcce92918c04f39d348e9c Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:39:35 +0100 Subject: [PATCH 05/42] less unnecessary comments, less lines of code :) --- examples/companion_radio/ui-new/UITask.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 886823ff..27734135 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -596,12 +596,9 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i setCurrScreen(msg_preview); if (_display != NULL) { - // Only turn on display if it's off AND not connected to phone via BLE - // If connected to phone, user will see messages there, so don't wake the OLED if (!_display->isOn() && !hasConnection()) { _display->turnOn(); } - // Always extend auto-off timer and trigger refresh if display is on if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _next_refresh = 100; // trigger refresh From 99a34731693432e844db03622ded3b739de705dc Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:41:11 +0100 Subject: [PATCH 06/42] even less comments \o/ --- examples/companion_radio/ui-orig/UITask.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 20d45bec..3b36e45d 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -136,12 +136,9 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i StrHelper::strncpy(_msg, text, sizeof(_msg)); if (_display != NULL) { - // Only turn on display if it's off AND not connected to phone via BLE - // If connected to phone, user will see messages there, so don't wake the OLED if (!_display->isOn() && !hasConnection()) { _display->turnOn(); } - // Always extend auto-off timer and trigger refresh if display is on if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _need_refresh = true; From d84e61546692be65c5c2f311fe4057b95b445ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sun, 23 Nov 2025 14:25:38 +0000 Subject: [PATCH 07/42] Add devcontainer configuration for vscode --- .devcontainer/devcontainer.json | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..b734fe6b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,42 @@ +{ + "name": "MeshCore", + "image": "mcr.microsoft.com/devcontainers/python:3-bookworm", + "features": { + "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { + "packages": [ + "sudo" + ] + } + }, + "runArgs": [ + "--network=host", + "--privileged", + "--volume", + "/dev/bus/usb:/dev/bus/usb" + ], + "postCreateCommand": { + "platformio": "pipx install platformio" + }, + "customizations": { + "vscode": { + "settings": { + "platformio-ide.disablePIOHomeStartup": true, + "editor.formatOnSave": false, + "workbench.colorCustomizations": { + "titleBar.activeBackground": "#0d1a2b", + "titleBar.activeForeground": "#ffffff", + "titleBar.inactiveBackground": "#0d1a2b99", + "titleBar.inactiveForeground": "#ffffff99" + } + }, + "extensions": [ + "platformio.platformio-ide", + "github.vscode-github-actions", + "GitHub.vscode-pull-request-github" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] + } + } +} \ No newline at end of file From d0f6def4f9b4d03ea226a9b2246aa594081a5049 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 27 Nov 2025 21:49:04 +0100 Subject: [PATCH 08/42] thinknode_m5: initial port --- src/helpers/ui/GxEPDDisplay.cpp | 8 + variants/thinknode_m5/ThinknodeM5Board.cpp | 40 ++++ variants/thinknode_m5/ThinknodeM5Board.h | 18 ++ variants/thinknode_m5/pins_arduino.h | 28 +++ variants/thinknode_m5/platformio.ini | 217 +++++++++++++++++++++ variants/thinknode_m5/target.cpp | 57 ++++++ variants/thinknode_m5/target.h | 32 +++ variants/thinknode_m5/variant.h | 21 ++ 8 files changed, 421 insertions(+) create mode 100644 variants/thinknode_m5/ThinknodeM5Board.cpp create mode 100644 variants/thinknode_m5/ThinknodeM5Board.h create mode 100644 variants/thinknode_m5/pins_arduino.h create mode 100644 variants/thinknode_m5/platformio.ini create mode 100644 variants/thinknode_m5/target.cpp create mode 100644 variants/thinknode_m5/target.h create mode 100644 variants/thinknode_m5/variant.h diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 34e31e30..a8a9b209 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -5,9 +5,17 @@ #define DISPLAY_ROTATION 3 #endif +#ifdef ESP32 + SPIClass SPI1 = SPIClass(FSPI); +#endif + bool GxEPDDisplay::begin() { display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); +#ifdef ESP32 + SPI1.begin(PIN_DISPLAY_SCLK, PIN_DISPLAY_MISO, PIN_DISPLAY_MOSI, PIN_DISPLAY_CS); +#else SPI1.begin(); +#endif display.init(115200, true, 2, false); display.setRotation(DISPLAY_ROTATION); setTextSize(1); // Default to size 1 diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp new file mode 100644 index 00000000..64744019 --- /dev/null +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -0,0 +1,40 @@ +#include "ThinknodeM5Board.h" + + + +void ThinknodeM5Board::begin() { + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle + delay(20); // allow power rail to discharge + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on + delay(120); // give display time to bias on cold boot + ESP32Board::begin(); + pinMode(PIN_STATUS_LED, OUTPUT); // init power led + } + + void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_deep_sleep_start(); + } + + void ThinknodeM5Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t ThinknodeM5Board::getBattMilliVolts() { + analogReadResolution(12); + analogSetPinAttenuation(PIN_VBAT_READ, ADC_11db); + + uint32_t mv = 0; + for (int i = 0; i < 8; ++i) { + mv += analogReadMilliVolts(PIN_VBAT_READ); + delayMicroseconds(200); + } + mv /= 8; + + analogReadResolution(10); + return static_cast(mv * ADC_MULTIPLIER ); +} + + const char* ThinknodeM5Board::getManufacturerName() const { + return "Elecrow ThinkNode M2"; + } diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h new file mode 100644 index 00000000..58a3ae30 --- /dev/null +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include + +class ThinknodeM5Board : public ESP32Board { + +public: + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; \ No newline at end of file diff --git a/variants/thinknode_m5/pins_arduino.h b/variants/thinknode_m5/pins_arduino.h new file mode 100644 index 00000000..a5dee363 --- /dev/null +++ b/variants/thinknode_m5/pins_arduino.h @@ -0,0 +1,28 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial +static const uint8_t TX = GPS_TX; +static const uint8_t RX = GPS_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = P_LORA_NSS; +static const uint8_t SCK = P_LORA_SCLK; +static const uint8_t MOSI = P_LORA_MISO; +static const uint8_t MISO = P_LORA_MOSI; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = PIN_BOARD_SCL; +static const uint8_t SDA = PIN_BOARD_SDA; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini new file mode 100644 index 00000000..54ad8bd5 --- /dev/null +++ b/variants/thinknode_m5/platformio.ini @@ -0,0 +1,217 @@ +[ThinkNode_M5] +extends = esp32_base +board = ESP32-S3-WROOM-1-N4 +build_flags = ${esp32_base.build_flags} + -I variants/thinknode_m5 + -D THINKNODE_M5 + -D GPS_RX=19 + -D GPS_TX=20 + -D PIN_VEXT_EN=46 + -D PIN_BUZZER=9 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_BOARD_SCL=1 + -D PIN_BOARD_SDA=2 + -D P_LORA_DIO_1=4 + -D P_LORA_NSS=17 + -D P_LORA_RESET=6 ; RADIOLIB_NC + -D P_LORA_BUSY=5 ; DIO2 = 38 + -D P_LORA_SCLK=16 + -D P_LORA_MISO=7 + -D P_LORA_MOSI=15 + -D PIN_USER_BTN=21 + -D PIN_STATUS_LED=1 + -D LED_STATE_ON=HIGH + -D PIN_LED=3 + -D DISPLAY_ROTATION=4 + -D DISPLAY_CLASS=GxEPDDisplay + -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 + -D EINK_SCALE_X=1.5625f + -D EINK_SCALE_Y=1.5625f + -D EINK_X_OFFSET=0 + -D EINK_Y_OFFSET=10 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 + -D MESH_DEBUG=1 +build_src_filter = ${esp32_base.build_src_filter} + + + + + + + +<../variants/thinknode_m5> +lib_deps = ${esp32_base.lib_deps} + zinggjm/GxEPD2 @ 1.6.2 + bakercp/CRC32 @ ^2.0.0 + +[env:ThinkNode_M5_Repeater] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"Thinknode M2 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +; [env:ThinkNode_M5_Repeater_bridge_rs232] +; extends = ThinkNode_M5 +; build_src_filter = ${ThinkNode_M5.build_src_filter} +; + +; +<../examples/simple_repeater/*.cpp> +; build_flags = +; ${ThinkNode_M5.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${ThinkNode_M5.lib_deps} +; ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_Repeater_bridge_espnow] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_room_server] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"Thinknode M2 Room Server"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_terminal_chat] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M5_companion_radio_ble] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_usb] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_wifi] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"Livebox-633C"' + -D WIFI_PWD='"vvQUHGSxsWd7fKMYSr"' +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_serial] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D SERIAL_TX=D6 + -D SERIAL_RX=D7 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp new file mode 100644 index 00000000..c65696c2 --- /dev/null +++ b/variants/thinknode_m5/target.cpp @@ -0,0 +1,57 @@ +#include +#include "target.h" + +ThinknodeM5Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); +// pinMode(21, INPUT); +// pinMode(48, OUTPUT); + #if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h new file mode 100644 index 00000000..75b68ae4 --- /dev/null +++ b/variants/thinknode_m5/target.h @@ -0,0 +1,32 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +//#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ThinknodeM5Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + + \ No newline at end of file diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h new file mode 100644 index 00000000..6118bd75 --- /dev/null +++ b/variants/thinknode_m5/variant.h @@ -0,0 +1,21 @@ +#define I2C_SCL 1 +#define I2C_SDA 2 +#define PIN_VBAT_READ 8 +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (2.11F) +#define PIN_BUZZER 9 +#define PIN_VEXT_EN_ACTIVE HIGH +#define PIN_VEXT_EN 46 +#define PIN_USER_BTN 21 +#define PIN_LED 3 +#define PIN_STATUS_LED 1 +#define PIN_PWRBTN 14 + +#define PIN_DISPLAY_MISO (-1) +#define PIN_DISPLAY_MOSI (45) +#define PIN_DISPLAY_SCLK (38) +#define PIN_DISPLAY_CS (39) +#define PIN_DISPLAY_DC (40) +#define PIN_DISPLAY_RST (41) +#define PIN_DISPLAY_BUSY (42) +#define DISP_BACKLIGHT (5) \ No newline at end of file From 24edd3cf209b9a8362d103b535d2f18c7958ae56 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 27 Nov 2025 22:55:21 +0100 Subject: [PATCH 09/42] thinknode_m5: add pca9557 expander --- variants/thinknode_m5/ThinknodeM5Board.cpp | 3 +-- variants/thinknode_m5/platformio.ini | 7 ++++--- variants/thinknode_m5/target.cpp | 6 ++++++ variants/thinknode_m5/target.h | 3 +++ variants/thinknode_m5/variant.h | 6 +++--- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 64744019..2aabb0e4 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -1,7 +1,6 @@ #include "ThinknodeM5Board.h" - void ThinknodeM5Board::begin() { pinMode(PIN_VEXT_EN, OUTPUT); digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle @@ -9,7 +8,7 @@ void ThinknodeM5Board::begin() { digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on delay(120); // give display time to bias on cold boot ESP32Board::begin(); - pinMode(PIN_STATUS_LED, OUTPUT); // init power led + // pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 54ad8bd5..a78b1ba2 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -19,9 +19,9 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 - -D PIN_STATUS_LED=1 - -D LED_STATE_ON=HIGH - -D PIN_LED=3 +# -D PIN_STATUS_LED=1 ; leds are on PCA !!! +# -D LED_STATE_ON=HIGH +# -D PIN_LED=3 -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 @@ -45,6 +45,7 @@ build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 + maxpromer/PCA9557-arduino [env:ThinkNode_M5_Repeater] extends = ThinkNode_M5 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index c65696c2..2c388b58 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -15,6 +15,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +PCA9557 expander (0x18, &Wire1); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -26,6 +27,11 @@ bool radio_init() { rtc_clock.begin(Wire); // pinMode(21, INPUT); // pinMode(48, OUTPUT); + Wire1.begin(48, 47); + expander.pinMode(4, OUTPUT); // eink + expander.pinMode(5, OUTPUT); // peripherals + expander.digitalWrite(4, HIGH); + expander.digitalWrite(5, HIGH); #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); return radio.std_init(&spi); diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 75b68ae4..7fa749f8 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -12,11 +12,14 @@ #include #include #endif +#include +#include extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +extern PCA9557 expander; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 6118bd75..d312fcbf 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -7,8 +7,8 @@ #define PIN_VEXT_EN_ACTIVE HIGH #define PIN_VEXT_EN 46 #define PIN_USER_BTN 21 -#define PIN_LED 3 -#define PIN_STATUS_LED 1 +//#define PIN_LED 3 +//#define PIN_STATUS_LED 1 #define PIN_PWRBTN 14 #define PIN_DISPLAY_MISO (-1) @@ -18,4 +18,4 @@ #define PIN_DISPLAY_DC (40) #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) -#define DISP_BACKLIGHT (5) \ No newline at end of file +//#define DISP_BACKLIGHT (5) \ No newline at end of file From dfec6d3483432159c34d734ad1d31b60f3e6b28b Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 09:57:58 +0100 Subject: [PATCH 10/42] thinknode_m5: tx_led --- variants/thinknode_m5/ThinknodeM5Board.cpp | 16 ++++++++++------ variants/thinknode_m5/ThinknodeM5Board.h | 9 +++++++++ variants/thinknode_m5/platformio.ini | 4 ++-- variants/thinknode_m5/target.cpp | 8 ++------ variants/thinknode_m5/target.h | 2 -- variants/thinknode_m5/variant.h | 3 ++- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 2aabb0e4..f178caad 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -1,14 +1,18 @@ #include "ThinknodeM5Board.h" +PCA9557 expander (0x18, &Wire1); void ThinknodeM5Board::begin() { - pinMode(PIN_VEXT_EN, OUTPUT); - digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle - delay(20); // allow power rail to discharge - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on - delay(120); // give display time to bias on cold boot + // Start expander + Wire1.begin(48, 47); + expander.pinMode(EXP_PIN_POWER, OUTPUT); // eink + expander.pinMode(EXP_PIN_BACKLIGHT, OUTPUT); // peripherals + expander.pinMode(EXP_PIN_LED, OUTPUT); // peripherals + expander.digitalWrite(EXP_PIN_POWER, HIGH); + expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); + expander.digitalWrite(EXP_PIN_LED, LOW); + ESP32Board::begin(); - // pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h index 58a3ae30..3c120027 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.h +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -4,6 +4,9 @@ #include #include #include +#include + +extern PCA9557 expander; class ThinknodeM5Board : public ESP32Board { @@ -15,4 +18,10 @@ public: uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; + void onBeforeTransmit() override { + expander.digitalWrite(EXP_PIN_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + expander.digitalWrite(EXP_PIN_LED, LOW); // turn TX LED off + } }; \ No newline at end of file diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index a78b1ba2..11f56377 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -6,11 +6,10 @@ build_flags = ${esp32_base.build_flags} -D THINKNODE_M5 -D GPS_RX=19 -D GPS_TX=20 - -D PIN_VEXT_EN=46 -D PIN_BUZZER=9 - -D PIN_VEXT_EN_ACTIVE=HIGH -D PIN_BOARD_SCL=1 -D PIN_BOARD_SDA=2 + -D P_LORA_EN=46 -D P_LORA_DIO_1=4 -D P_LORA_NSS=17 -D P_LORA_RESET=6 ; RADIOLIB_NC @@ -19,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 + -D EXP_PIN_LED=1 # -D PIN_STATUS_LED=1 ; leds are on PCA !!! # -D LED_STATE_ON=HIGH # -D PIN_LED=3 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index 2c388b58..fdc5ca7a 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -15,7 +15,6 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; -PCA9557 expander (0x18, &Wire1); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -27,11 +26,8 @@ bool radio_init() { rtc_clock.begin(Wire); // pinMode(21, INPUT); // pinMode(48, OUTPUT); - Wire1.begin(48, 47); - expander.pinMode(4, OUTPUT); // eink - expander.pinMode(5, OUTPUT); // peripherals - expander.digitalWrite(4, HIGH); - expander.digitalWrite(5, HIGH); + pinMode(P_LORA_EN, OUTPUT); + digitalWrite(P_LORA_EN, HIGH); #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); return radio.std_init(&spi); diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 7fa749f8..c3584a70 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -12,8 +12,6 @@ #include #include #endif -#include -#include extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index d312fcbf..7ee5f5cc 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -18,4 +18,5 @@ #define PIN_DISPLAY_DC (40) #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) -//#define DISP_BACKLIGHT (5) \ No newline at end of file +#define EXP_PIN_BACKLIGHT (5) +#define EXP_PIN_POWER (4) \ No newline at end of file From ee4e87c3ee545821a9b017e73525a3f943d9f2ba Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 10:33:19 +0100 Subject: [PATCH 11/42] thinknode_m5: manage baclight --- examples/companion_radio/ui-new/UITask.cpp | 6 +++++- src/helpers/ui/GxEPDDisplay.cpp | 9 +++++++++ variants/thinknode_m5/ThinknodeM5Board.cpp | 3 +-- variants/thinknode_m5/platformio.ini | 3 +++ variants/thinknode_m5/target.cpp | 2 -- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 16751d20..fe26277f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -716,10 +716,14 @@ void UITask::loop() { _analogue_pin_read_millis = millis(); } #endif -#if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN) +#if defined(BACKLIGHT_BTN) if (millis() > next_backlight_btn_check) { bool touch_state = digitalRead(PIN_BUTTON2); +#if defined(DISP_BACKLIGHT) digitalWrite(DISP_BACKLIGHT, !touch_state); +#elif defined(EXP_PIN_BACKLIGHT) + expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state); +#endif next_backlight_btn_check = millis() + 300; } #endif diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index a8a9b209..ad47754b 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -1,6 +1,11 @@ #include "GxEPDDisplay.h" +#ifdef EXP_PIN_BACKLIGHT + #include + extern PCA9557 expander; +#endif + #ifndef DISPLAY_ROTATION #define DISPLAY_ROTATION 3 #endif @@ -35,6 +40,8 @@ void GxEPDDisplay::turnOn() { if (!_init) begin(); #if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN) digitalWrite(DISP_BACKLIGHT, HIGH); +#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN) + expander.digitalWrite(EXP_PIN_BACKLIGHT, HIGH); #endif _isOn = true; } @@ -42,6 +49,8 @@ void GxEPDDisplay::turnOn() { void GxEPDDisplay::turnOff() { #if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN) digitalWrite(DISP_BACKLIGHT, LOW); +#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN) + expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); #endif _isOn = false; } diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index f178caad..916f4483 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -3,7 +3,7 @@ PCA9557 expander (0x18, &Wire1); void ThinknodeM5Board::begin() { - // Start expander + // Start expander and configure pins Wire1.begin(48, 47); expander.pinMode(EXP_PIN_POWER, OUTPUT); // eink expander.pinMode(EXP_PIN_BACKLIGHT, OUTPUT); // peripherals @@ -11,7 +11,6 @@ void ThinknodeM5Board::begin() { expander.digitalWrite(EXP_PIN_POWER, HIGH); expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); expander.digitalWrite(EXP_PIN_LED, LOW); - ESP32Board::begin(); } diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 11f56377..353a9c52 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -18,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 + -D PIN_BUTTON2=14 -D EXP_PIN_LED=1 # -D PIN_STATUS_LED=1 ; leds are on PCA !!! # -D LED_STATE_ON=HIGH @@ -29,6 +30,8 @@ build_flags = ${esp32_base.build_flags} -D EINK_SCALE_Y=1.5625f -D EINK_X_OFFSET=0 -D EINK_Y_OFFSET=10 + -D BACKLIGHT_BTN=PIN_BUTTON2 + -D AUTO_OFF_MILLIS=0 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=3.3 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index fdc5ca7a..fa559610 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -24,8 +24,6 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); -// pinMode(21, INPUT); -// pinMode(48, OUTPUT); pinMode(P_LORA_EN, OUTPUT); digitalWrite(P_LORA_EN, HIGH); #if defined(P_LORA_SCLK) From 1c0017b634a68a12e232c46b2c7b4d4d47759858 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 11:11:13 +0100 Subject: [PATCH 12/42] thinknode_m5: gps support --- examples/companion_radio/ui-new/UITask.cpp | 15 +++++++++++++-- .../sensors/EnvironmentSensorManager.cpp | 4 ++++ variants/thinknode_m5/ThinknodeM5Board.cpp | 5 +++++ variants/thinknode_m5/pins_arduino.h | 4 ++-- variants/thinknode_m5/platformio.ini | 19 +++++++++++-------- variants/thinknode_m5/target.cpp | 9 ++++++++- variants/thinknode_m5/target.h | 4 +++- variants/thinknode_m5/variant.h | 8 +++++++- 8 files changed, 53 insertions(+), 15 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index fe26277f..59a1b2de 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -260,13 +260,24 @@ public: #if ENV_INCLUDE_GPS == 1 } else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); + char buf[50]; int y = 18; - display.drawTextLeftAlign(0, y, _task->getGPSState() ? "gps on" : "gps off"); + bool gps_state = _task->getGPSState(); +#ifdef PIN_GPS_SWITCH + bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); + if (gps_state != hw_gps_state) { + strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); + } else { + strcpy(buf, gps_state ? "gps on" : "gps off"); + } +#else + strcpy(buf, gps_state ? "gps on" : "gps off"); +#endif + display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { y = y + 12; display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { - char buf[50]; strcpy(buf, nmea->isValid()?"fix":"no fix"); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 79dc87e5..bb675c27 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -548,7 +548,11 @@ void EnvironmentSensorManager::initBasicGPS() { delay(1000); // We'll consider GPS detected if we see any data on Serial1 +#ifdef ENV_SKIP_GPS_DETECT + gps_detected = true; +#else gps_detected = (Serial1.available() > 0); +#endif if (gps_detected) { MESH_DEBUG_PRINTLN("GPS detected"); diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 916f4483..5adc8c00 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -11,6 +11,11 @@ void ThinknodeM5Board::begin() { expander.digitalWrite(EXP_PIN_POWER, HIGH); expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); expander.digitalWrite(EXP_PIN_LED, LOW); + +#ifdef PIN_GPS_SWITCH + pinMode(PIN_GPS_SWITCH, INPUT); +#endif + ESP32Board::begin(); } diff --git a/variants/thinknode_m5/pins_arduino.h b/variants/thinknode_m5/pins_arduino.h index a5dee363..408ed236 100644 --- a/variants/thinknode_m5/pins_arduino.h +++ b/variants/thinknode_m5/pins_arduino.h @@ -12,8 +12,8 @@ #define USB_PID 0x1001 // Serial -static const uint8_t TX = GPS_TX; -static const uint8_t RX = GPS_RX; +static const uint8_t TX = PIN_GPS_TX; +static const uint8_t RX = PIN_GPS_RX; // Default SPI will be mapped to Radio static const uint8_t SS = P_LORA_NSS; diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 353a9c52..23db506a 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,9 +3,8 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 + -I src/helpres/sensors -D THINKNODE_M5 - -D GPS_RX=19 - -D GPS_TX=20 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 -D PIN_BOARD_SDA=2 @@ -19,10 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 -D PIN_BUTTON2=14 - -D EXP_PIN_LED=1 -# -D PIN_STATUS_LED=1 ; leds are on PCA !!! -# -D LED_STATE_ON=HIGH -# -D PIN_LED=3 + -D EXP_PIN_LED=1 ; led is on bus expander -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 @@ -32,6 +28,7 @@ build_flags = ${esp32_base.build_flags} -D EINK_Y_OFFSET=10 -D BACKLIGHT_BTN=PIN_BUTTON2 -D AUTO_OFF_MILLIS=0 + -D DISABLE_DIAGNOSTIC_OUTPUT -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=3.3 -D SX126X_CURRENT_LIMIT=140 @@ -40,7 +37,11 @@ build_flags = ${esp32_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_RX_BOOSTED_GAIN=1 -D MESH_DEBUG=1 + -D ENV_INCLUDE_GPS=1 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 build_src_filter = ${esp32_base.build_src_filter} + + + + + @@ -49,6 +50,7 @@ lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 maxpromer/PCA9557-arduino + stevemarple/MicroNMEA @ ^2.0.6 [env:ThinkNode_M5_Repeater] extends = ThinkNode_M5 @@ -109,7 +111,7 @@ lib_deps = ${esp32_ota.lib_deps} [env:ThinkNode_M5_room_server] -extends = ThinkNode_M5 +extends = ThinkNonde_M5 build_src_filter = ${ThinkNode_M5.build_src_filter} +<../examples/simple_room_server> build_flags = @@ -148,9 +150,10 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 +; -D GPS_NMEA_DEBUG build_src_filter = ${ThinkNode_M5.build_src_filter} + + diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index fa559610..8208d2c4 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -1,5 +1,6 @@ #include #include "target.h" +#include ThinknodeM5Board board; @@ -14,7 +15,13 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index c3584a70..2af42095 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #ifdef DISPLAY_CLASS #include #include @@ -16,7 +18,7 @@ extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; extern PCA9557 expander; #ifdef DISPLAY_CLASS diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 7ee5f5cc..9b82416b 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -19,4 +19,10 @@ #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) #define EXP_PIN_BACKLIGHT (5) -#define EXP_PIN_POWER (4) \ No newline at end of file +#define EXP_PIN_POWER (4) + +#define PIN_GPS_EN (11) +#define PIN_GPS_RESET (13) +#define PIN_GPS_RX (20) +#define PIN_GPS_TX (19) +#define PIN_GPS_SWITCH (10) \ No newline at end of file From c641beabd32f564f9064a520efd90afd1df524fa Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 16:37:10 +0800 Subject: [PATCH 13/42] https://github.com/meshcore-dev/MeshCore/issues/989 - persist GPS enabled state to preferences Add GPS configuration to NodePrefs structure and persist the GPS enabled state when toggled via UI. This ensures GPS settings are retained across device restarts. --- examples/companion_radio/DataStore.cpp | 4 ++++ examples/companion_radio/MyMesh.cpp | 7 +++++++ examples/companion_radio/NodePrefs.h | 2 ++ examples/companion_radio/ui-new/UITask.cpp | 3 +++ 4 files changed, 16 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 00e25cb2..7f5761f3 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -222,6 +222,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read(pad, 2); // 78 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 + file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 + file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.close(); } @@ -254,6 +256,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write(pad, 2); // 78 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 + file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 + file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 09d866c9..a94975b3 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -739,6 +739,8 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; + _prefs.gps_enabled = 0; // GPS disabled by default + _prefs.gps_interval = 0; // No automatic GPS updates by default //_prefs.rx_delay_base = 10.0f; enable once new algo fixed } @@ -776,6 +778,7 @@ void MyMesh::begin(bool has_display) { _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); + _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -803,6 +806,10 @@ void MyMesh::begin(bool has_display) { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); + +#if ENV_INCLUDE_GPS == 1 + sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); +#endif } const char *MyMesh::getNodeName() { diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 13c9f884..e9db5444 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -25,4 +25,6 @@ struct NodePrefs { // persisted to file uint32_t ble_pin; uint8_t advert_loc_policy; uint8_t buzzer_quiet; + uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) + uint32_t gps_interval; // GPS read interval in seconds }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 59a1b2de..4cc47e23 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -863,13 +863,16 @@ void UITask::toggleGPS() { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { _sensors->setSettingValue("gps", "0"); + _node_prefs->gps_enabled = 0; notify(UIEventType::ack); showAlert("GPS: Disabled", 800); } else { _sensors->setSettingValue("gps", "1"); + _node_prefs->gps_enabled = 1; notify(UIEventType::ack); showAlert("GPS: Enabled", 800); } + the_mesh.savePrefs(); _next_refresh = 0; break; } From 88fb173297d667894df4b21faffab84ddea14d7a Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 17:20:02 +0800 Subject: [PATCH 14/42] add configurable GPS update interval Make GPS update interval configurable via settings instead of using hardcoded 1 second value. The interval is persisted from preferences and can be adjusted at runtime through the sensor manager settings interface --- examples/companion_radio/MyMesh.cpp | 3 +++ src/helpers/sensors/EnvironmentSensorManager.cpp | 11 ++++++++++- src/helpers/sensors/EnvironmentSensorManager.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a94975b3..be39b7fa 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -809,6 +809,9 @@ void MyMesh::begin(bool has_display) { #if ENV_INCLUDE_GPS == 1 sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval_str); #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index bb675c27..53c381f7 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -521,6 +521,15 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val } return true; } + if (strcmp(name, "gps_interval") == 0) { + uint32_t interval_seconds = atoi(value); + if (interval_seconds > 0) { + gps_update_interval_ms = interval_seconds * 1000; + } else { + gps_update_interval_ms = 1000; // Default to 1 second if 0 + } + return true; + } #endif return false; // not supported } @@ -708,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + 1000; + next_gps_update = millis() + gps_update_interval_ms; } #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 5f1c08e2..6dd532e6 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -25,6 +25,7 @@ protected: bool gps_detected = false; bool gps_active = false; + uint32_t gps_update_interval_ms = 1000; // Default 1 second #if ENV_INCLUDE_GPS LocationProvider* _location; From 678915ef3b6ad3e9bb398bfa797b5c6f832f73f5 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 17:29:59 +0800 Subject: [PATCH 15/42] add GPS interval validation and bounds checking --- examples/companion_radio/MyMesh.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index be39b7fa..91bb7358 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -779,6 +779,7 @@ void MyMesh::begin(bool has_display) { _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 + _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -809,9 +810,11 @@ void MyMesh::begin(bool has_display) { #if ENV_INCLUDE_GPS == 1 sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); - char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) - sprintf(interval_str, "%u", _prefs.gps_interval); - sensors.setSettingValue("gps_interval", interval_str); + if (_prefs.gps_interval > 0) { + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval_str); + } #endif } From 4aebc57add0c61a37e31bcf18b8a7c5620539785 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 18:02:08 +0800 Subject: [PATCH 16/42] fixed gps init value --- examples/companion_radio/MyMesh.cpp | 9 --------- examples/companion_radio/main.cpp | 10 ++++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 91bb7358..035e06a7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -807,15 +807,6 @@ void MyMesh::begin(bool has_display) { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); - -#if ENV_INCLUDE_GPS == 1 - sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); - if (_prefs.gps_interval > 0) { - char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) - sprintf(interval_str, "%u", _prefs.gps_interval); - sensors.setSettingValue("gps_interval", interval_str); - } -#endif } const char *MyMesh::getNodeName() { diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d..15b461f3 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -216,6 +216,16 @@ void setup() { sensors.begin(); +#if ENV_INCLUDE_GPS == 1 + // Apply GPS preferences after sensors.begin() so gps_detected is set + sensors.setSettingValue("gps", the_mesh.getNodePrefs()->gps_enabled ? "1" : "0"); + if (the_mesh.getNodePrefs()->gps_interval > 0) { + char interval_str[12]; + sprintf(interval_str, "%u", the_mesh.getNodePrefs()->gps_interval); + sensors.setSettingValue("gps_interval", interval_str); + } +#endif + #ifdef DISPLAY_CLASS ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved #endif From 39503ad0b48515e527d38ff2bf477b207b5bd1b5 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 18:35:10 +0800 Subject: [PATCH 17/42] move GPS preference initialization to UITask --- examples/companion_radio/main.cpp | 10 ---------- examples/companion_radio/ui-new/UITask.cpp | 13 +++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 15b461f3..82c8c21d 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -216,16 +216,6 @@ void setup() { sensors.begin(); -#if ENV_INCLUDE_GPS == 1 - // Apply GPS preferences after sensors.begin() so gps_detected is set - sensors.setSettingValue("gps", the_mesh.getNodePrefs()->gps_enabled ? "1" : "0"); - if (the_mesh.getNodePrefs()->gps_interval > 0) { - char interval_str[12]; - sprintf(interval_str, "%u", the_mesh.getNodePrefs()->gps_interval); - sensors.setSettingValue("gps_interval", interval_str); - } -#endif - #ifdef DISPLAY_CLASS ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved #endif diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 4cc47e23..ebdeb6d3 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -537,6 +537,19 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #endif _node_prefs = node_prefs; + +#if ENV_INCLUDE_GPS == 1 + // Apply GPS preferences from stored prefs + if (_sensors != NULL && _node_prefs != NULL) { + _sensors->setSettingValue("gps", _node_prefs->gps_enabled ? "1" : "0"); + if (_node_prefs->gps_interval > 0) { + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _node_prefs->gps_interval); + _sensors->setSettingValue("gps_interval", interval_str); + } + } +#endif + if (_display != NULL) { _display->turnOn(); } From 62e180dc0fa1b5af9e55308bc91d60ec1fc98ba8 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 19:02:00 +0800 Subject: [PATCH 18/42] changed ms to sec --- src/helpers/sensors/EnvironmentSensorManager.cpp | 6 +++--- src/helpers/sensors/EnvironmentSensorManager.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 53c381f7..2362eda7 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -524,9 +524,9 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val if (strcmp(name, "gps_interval") == 0) { uint32_t interval_seconds = atoi(value); if (interval_seconds > 0) { - gps_update_interval_ms = interval_seconds * 1000; + gps_update_interval_sec = interval_seconds; } else { - gps_update_interval_ms = 1000; // Default to 1 second if 0 + gps_update_interval_sec = 1; // Default to 1 second if 0 } return true; } @@ -717,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + gps_update_interval_ms; + next_gps_update = millis() + gps_update_interval_sec; } #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 6dd532e6..f176a33f 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -25,7 +25,7 @@ protected: bool gps_detected = false; bool gps_active = false; - uint32_t gps_update_interval_ms = 1000; // Default 1 second + uint32_t gps_update_interval_sec = 1; // Default 1 second #if ENV_INCLUDE_GPS LocationProvider* _location; From df3cb3d192ab655b467ecb22e4fb41c3041c1673 Mon Sep 17 00:00:00 2001 From: csrutil Date: Sat, 29 Nov 2025 20:29:52 +0800 Subject: [PATCH 19/42] _location->loop() should be in the next tick --- src/helpers/sensors/EnvironmentSensorManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2362eda7..b072bcb0 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -695,9 +695,9 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS - _location->loop(); - if (millis() > next_gps_update) { + _location->loop(); + if(gps_active){ #ifdef RAK_WISBLOCK_GPS if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) { @@ -717,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + gps_update_interval_sec; + next_gps_update = millis() + (gps_update_interval_sec * 1000); } #endif } From cfb7ed876ca7a424d7d1007ebb271aa08765e77b Mon Sep 17 00:00:00 2001 From: csrutil Date: Sun, 30 Nov 2025 09:45:56 +0800 Subject: [PATCH 20/42] CMD_SET_CUSTOM_VAR will update gps and gps_interval --- examples/companion_radio/MyMesh.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 035e06a7..3aed2da7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1525,6 +1525,17 @@ void MyMesh::handleCmdFrame(size_t len) { *np++ = 0; // modify 'cmd_frame', replace ':' with null bool success = sensors.setSettingValue(sp, np); if (success) { + #if ENV_INCLUDE_GPS == 1 + // Update node preferences for GPS settings + if (strcmp(sp, "gps") == 0) { + _prefs.gps_enabled = (np[0] == '1') ? 1 : 0; + savePrefs(); + } else if (strcmp(sp, "gps_interval") == 0) { + uint32_t interval_seconds = atoi(np); + _prefs.gps_interval = constrain(interval_seconds, 0, 86400); + savePrefs(); + } + #endif writeOKFrame(); } else { writeErrFrame(ERR_CODE_ILLEGAL_ARG); From e054597a189cd7749c5327351d27e9dc92d0d125 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sun, 30 Nov 2025 18:32:10 +1100 Subject: [PATCH 21/42] * ver 1.11.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 9c22532d..1fcc5697 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "13 Nov 2025" +#define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.10.0" +#define FIRMWARE_VERSION "v1.11.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 98bce787..ed9f0c5f 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -68,11 +68,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 8641caaf..e7f1fee8 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 9259ad9c..c320eb44 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #define FIRMWARE_ROLE "sensor" From 052f17738cd05e133265f3370f039497311bfe90 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Sun, 30 Nov 2025 10:52:33 +0100 Subject: [PATCH 22/42] add default LED_STATE_ON for boards that don't have it defined --- examples/companion_radio/ui-new/UITask.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 32d5f3a0..02c3cafb 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -8,6 +8,10 @@ #include #include +#ifndef LED_STATE_ON + #define LED_STATE_ON 1 +#endif + #ifdef PIN_BUZZER #include #endif @@ -50,7 +54,7 @@ class UITask : public AbstractUITask { UIScreen* curr; void userLedHandler(); - + // Button action handlers char checkDisplayOn(char c); char handleLongPress(char c); From 405f703bfebf5218355d0c07fd9f948ed4c40ef2 Mon Sep 17 00:00:00 2001 From: Florent Date: Mon, 1 Dec 2025 09:40:02 +0100 Subject: [PATCH 23/42] thinknode_m5: fix repeater build --- variants/thinknode_m5/platformio.ini | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 23db506a..fb2ba3ac 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,7 +3,7 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 - -I src/helpres/sensors + -I src/helpers/sensors -D THINKNODE_M5 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 @@ -44,7 +44,6 @@ build_src_filter = ${esp32_base.build_src_filter} + + + - + +<../variants/thinknode_m5> lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 @@ -159,6 +158,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -177,6 +177,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -198,6 +199,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -219,6 +221,8 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + + lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 From 07d6484b616ef86f340b5faf94bd43fcb415735a Mon Sep 17 00:00:00 2001 From: Florian Lippert <973586+flol@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:29:03 +0100 Subject: [PATCH 24/42] Support for RAK11310 WisBlock --- variants/rak11310/RAK11310Board.cpp | 30 +++++++ variants/rak11310/RAK11310Board.h | 49 +++++++++++ variants/rak11310/platformio.ini | 132 ++++++++++++++++++++++++++++ variants/rak11310/target.cpp | 39 ++++++++ variants/rak11310/target.h | 20 +++++ 5 files changed, 270 insertions(+) create mode 100644 variants/rak11310/RAK11310Board.cpp create mode 100644 variants/rak11310/RAK11310Board.h create mode 100644 variants/rak11310/platformio.ini create mode 100644 variants/rak11310/target.cpp create mode 100644 variants/rak11310/target.h diff --git a/variants/rak11310/RAK11310Board.cpp b/variants/rak11310/RAK11310Board.cpp new file mode 100644 index 00000000..f45d8148 --- /dev/null +++ b/variants/rak11310/RAK11310Board.cpp @@ -0,0 +1,30 @@ +#include "RAK11310Board.h" + +#include +#include + +void RAK11310Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); +#endif + +#ifdef PIN_VBAT_READ + pinMode(PIN_VBAT_READ, INPUT); +#endif + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setSDA(PIN_BOARD_SDA); + Wire.setSCL(PIN_BOARD_SCL); +#endif + + Wire.begin(); + + delay(10); // give sx1262 some time to power up +} + +bool RAK11310Board::startOTAUpdate(const char *id, char reply[]) { + return false; +} diff --git a/variants/rak11310/RAK11310Board.h b/variants/rak11310/RAK11310Board.h new file mode 100644 index 00000000..ea0f15e2 --- /dev/null +++ b/variants/rak11310/RAK11310Board.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +// from https://github.com/RAKWireless/RAK11300-AT-Command-Firmware/blob/9c48409a43620a828d653501d536473200aa33af/RAK11300-AT-Arduino/batt.cpp#L17-L19 +#define VBAT_MV_PER_LSB (0.806F) // 3.0V ADC range and 12 - bit ADC resolution = 3300mV / 4096 +#define VBAT_DIVIDER (0.6F) // 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) +#define VBAT_DIVIDER_COMP (1.846F) // // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ 26 +#define BATTERY_SAMPLES 8 +#define ADC_MULTIPLIER (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class RAK11310Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#ifdef P_LORA_TX_LED + void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); } + void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); } +#endif + + uint16_t getBattMilliVolts() override { +#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER) + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + return (ADC_MULTIPLIER * raw); +#else + return 0; +#endif + } + + const char *getManufacturerName() const override { return "RAK 11310"; } + + void reboot() override { rp2040.reboot(); } + + bool startOTAUpdate(const char *id, char reply[]) override; +}; diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini new file mode 100644 index 00000000..df99ea84 --- /dev/null +++ b/variants/rak11310/platformio.ini @@ -0,0 +1,132 @@ +; RAK11310 +; Pinout from https://github.com/beegee-tokyo/SX126x-Arduino/blob/6be1f87b84ad4d445a38ec53d65be4425f2383f3/src/boards/mcu/board.cpp#L259 + +[rak11310] +extends = rp2040_base +board = rakwireless_rak11300 +board_build.filesystem_size = 0.5m +build_flags = ${rp2040_base.build_flags} + -I variants/rak11310 + -D ARDUINO_RAKWIRELESS_RAK11300=1 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=29 + -D P_LORA_NSS=13 ; CS + -D P_LORA_RESET=14 + -D P_LORA_BUSY=15 + -D P_LORA_SCLK=10 + -D P_LORA_MISO=12 + -D P_LORA_MOSI=11 + -D P_LORA_TX_LED=24 ; green led = 23, blue led = 24 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 +; Debug options + ; -D DEBUG_RP2040_WIRE=1 + ; -D DEBUG_RP2040_SPI=1 + ; -D DEBUG_RP2040_CORE=1 + ; -D RADIOLIB_DEBUG_SPI=1 + ; -D DEBUG_RP2040_PORT=Serial +build_src_filter = ${rp2040_base.build_src_filter} + + + +<../variants/rak11310> +lib_deps = ${rp2040_base.lib_deps} + +[env:rak11310_repeater] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D ADVERT_NAME='"RAK11310 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_repeater> + +[env:rak11310_repeater_bridge_rs232] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=8 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + + + +<../examples/simple_repeater> + +[env:rak11310_room_server] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D ADVERT_NAME='"RAK11310 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_room_server> + +[env:rak11310_companion_radio_usb] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = ${rak11310.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; [env:rak11310_companion_radio_ble] +; extends = rak11310 +; build_flags = ${rak11310.build_flags} +; -D MAX_CONTACTS=100 +; -D MAX_GROUP_CHANNELS=8 +; -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${rak11310.build_src_filter} +; +<../examples/companion_radio/*.cpp> +; lib_deps = ${rak11310.lib_deps} +; densaugeo/base64 @ ~1.4.0 + +; [env:rak11310_companion_radio_wifi] +; extends = rak11310 +; build_flags = ${rak11310.build_flags} +; -D MAX_CONTACTS=100 +; -D MAX_GROUP_CHANNELS=8 +; -D WIFI_DEBUG_LOGGING=1 +; -D WIFI_SSID='"myssid"' +; -D WIFI_PWD='"mypwd"' +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${rak11310.build_src_filter} +; +<../examples/companion_radio/*.cpp> +; lib_deps = ${rak11310.lib_deps} +; densaugeo/base64 @ ~1.4.0 + +[env:rak11310_terminal_chat] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${rak11310.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/rak11310/target.cpp b/variants/rak11310/target.cpp new file mode 100644 index 00000000..dba5bff2 --- /dev/null +++ b/variants/rak11310/target.cpp @@ -0,0 +1,39 @@ +#include +#include "target.h" +#include + +RAK11310Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI1); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI1); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/rak11310/target.h b/variants/rak11310/target.h new file mode 100644 index 00000000..fe45c3f2 --- /dev/null +++ b/variants/rak11310/target.h @@ -0,0 +1,20 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +extern RAK11310Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From f56172738d3c24b82f27b2ae88dc4c8b58d64027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 2 Dec 2025 10:30:45 +0000 Subject: [PATCH 25/42] Bridge: Fix RAK4631 serial2 GPS conflict --- variants/rak4631/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index b3357855..7293b4d4 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -81,6 +81,7 @@ build_flags = -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX -D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX + -UENV_INCLUDE_GPS ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 From 69a9a0bce9db9896b155d5c13f0c2d2a3875bc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 2 Dec 2025 10:31:24 +0000 Subject: [PATCH 26/42] Bridge: Add t114 rs232 targets --- src/helpers/bridges/RS232Bridge.cpp | 8 +++--- src/helpers/bridges/RS232Bridge.h | 2 +- variants/heltec_t114/platformio.ini | 38 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 77332855..0024f6f2 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -15,10 +15,9 @@ void RS232Bridge::begin() { #if defined(ESP32) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); -#elif defined(RAK_4631) - ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(NRF52_PLATFORM) - ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); + // Tested with RAK_4631 and T114 + ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(RP2040_PLATFORM) ((SerialUART *)_serial)->setRX(WITH_RS232_BRIDGE_RX); ((SerialUART *)_serial)->setTX(WITH_RS232_BRIDGE_TX); @@ -123,8 +122,7 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) { // Check if packet fits within our maximum payload size if (len > (MAX_TRANS_UNIT + 1)) { - BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, - MAX_TRANS_UNIT + 1); + BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, MAX_TRANS_UNIT + 1); return; } diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 839c0ba0..8fc1c22c 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -40,7 +40,7 @@ * Platform Support: * Different platforms require different pin configuration methods: * - ESP32: Uses HardwareSerial::setPins(rx, tx) - * - NRF52: Uses HardwareSerial::setPins(rx, tx) + * - NRF52: Uses Uart::setPins(rx, tx) * - RP2040: Uses SerialUART::setRX(rx) and SerialUART::setTX(tx) * - STM32: Uses HardwareSerial::setRx(rx) and HardwareSerial::setTx(tx) */ diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 91ca78cd..7b6f5cee 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -59,6 +59,25 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +[env:Heltec_t114_without_display_repeater_bridge_rs232] +extends = Heltec_t114 +build_flags = + ${Heltec_t114.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=10 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_t114.build_src_filter} + + + +<../examples/simple_repeater> + [env:Heltec_t114_without_display_room_server] extends = Heltec_t114 build_src_filter = ${Heltec_t114.build_src_filter} @@ -151,6 +170,25 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +[env:Heltec_t114_repeater_bridge_rs232] +extends = Heltec_t114 +build_flags = + ${Heltec_t114.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=10 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_t114_with_display.build_src_filter} + + + +<../examples/simple_repeater> + [env:Heltec_t114_room_server] extends = Heltec_t114_with_display build_src_filter = ${Heltec_t114_with_display.build_src_filter} From dde9b7cc76ece24c753768c63fcdfa5bdbbd2cb3 Mon Sep 17 00:00:00 2001 From: taco Date: Wed, 3 Dec 2025 14:59:57 +1100 Subject: [PATCH 27/42] remove calls to sd_power_mode_set(NRF_POWER_MODE_LOWPWR); this is the default mode, there is no need to call it unless previously changing it. --- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp | 2 -- variants/wio_wm1110/WioWM1110Board.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp index c41a6bc0..561ed504 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp @@ -11,8 +11,6 @@ void MinewsemiME25LS01Board::begin() { pinMode(PIN_VBAT_READ, INPUT); - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - #ifdef BUTTON_PIN pinMode(BUTTON_PIN, INPUT); pinMode(LED_PIN, OUTPUT); diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index ca3638b3..98e0d616 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -23,7 +23,6 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void WioWM1110Board::begin() { startup_reason = BD_STARTUP_NORMAL; - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); NRF_POWER->DCDCEN = 1; pinMode(BATTERY_PIN, INPUT); From e1d3da942be1a4ec98ec37c1a1741084bcf1819c Mon Sep 17 00:00:00 2001 From: taco Date: Wed, 3 Dec 2025 15:58:36 +1100 Subject: [PATCH 28/42] fix DC/DC enable for boards which currently have it. this fixes how the reg1 dc/dc converter is enabled on WisMesh Tag / T1000e / WM1110 and Xiao NRF52 --- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 10 +++++++++- variants/t1000-e/T1000eBoard.cpp | 10 +++++++--- variants/wio_wm1110/WioWM1110Board.cpp | 9 ++++++++- variants/xiao_nrf52/XiaoNrf52Board.cpp | 10 +++++++++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 28f6f713..68638f0d 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -21,7 +21,15 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void RAKWismeshTagBoard::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } pinMode(PIN_VBAT_READ, INPUT); pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index 4bcdf98a..a41abd92 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -9,10 +9,14 @@ void T1000eBoard::begin() { startup_reason = BD_STARTUP_NORMAL; btn_prev_state = HIGH; - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - // Enable DC/DC converter for improved power efficiency - NRF_POWER->DCDCEN = 1; + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } #ifdef BUTTON_PIN pinMode(BATTERY_PIN, INPUT); diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index 98e0d616..153d476c 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -23,7 +23,14 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void WioWM1110Board::begin() { startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } pinMode(BATTERY_PIN, INPUT); pinMode(LED_GREEN, OUTPUT); diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 396880ab..69218926 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -23,7 +23,15 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void XiaoNrf52Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); From ec375fa24847c25965c5e0c3ac0a87eaf3bcb752 Mon Sep 17 00:00:00 2001 From: Luis Garcia Date: Tue, 2 Dec 2025 17:41:31 -0700 Subject: [PATCH 29/42] variants: lilygo_techo: variant: Turn off leds on poweroff Signed-off-by: Luis Garcia --- variants/lilygo_techo/TechoBoard.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 4792153a..08038797 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -32,13 +32,13 @@ public: void powerOff() override { #ifdef LED_RED - digitalWrite(LED_RED, LOW); + digitalWrite(LED_RED, HIGH); #endif #ifdef LED_GREEN - digitalWrite(LED_GREEN, LOW); + digitalWrite(LED_GREEN, HIGH); #endif #ifdef LED_BLUE - digitalWrite(LED_BLUE, LOW); + digitalWrite(LED_BLUE, HIGH); #endif #ifdef DISP_BACKLIGHT digitalWrite(DISP_BACKLIGHT, LOW); From 1a3f7a7ea985289ed7d2e430e131b1ea4f6739f9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:47:41 +0100 Subject: [PATCH 30/42] Fix BLE semaphore leak in Bluefruit library Patches Bluefruit library to fix semaphore leak bug that causes device lockup when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout). Co-authored-by: Liam Cottle Co-authored-by: oltaco --- arch/nrf52/extra_scripts/patch_bluefruit.py | 198 ++++++++++++++++++++ platformio.ini | 4 +- 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 arch/nrf52/extra_scripts/patch_bluefruit.py diff --git a/arch/nrf52/extra_scripts/patch_bluefruit.py b/arch/nrf52/extra_scripts/patch_bluefruit.py new file mode 100644 index 00000000..b43bffb5 --- /dev/null +++ b/arch/nrf52/extra_scripts/patch_bluefruit.py @@ -0,0 +1,198 @@ +""" +Bluefruit BLE Patch Script + +Patches Bluefruit library to fix semaphore leak bug that causes device lockup +when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout). + +Patches applied: +1. BLEConnection.h: Add _hvn_qsize member to track semaphore queue size +2. BLEConnection.cpp: Store hvn_qsize and restore semaphore on disconnect + +Bug description: +- When a BLE central disconnects unexpectedly (reason=8 supervision timeout), + the BLE_GATTS_EVT_HVN_TX_COMPLETE event may never fire +- This leaves the _hvn_sem counting semaphore in a decremented state +- Since BLEConnection objects are reused (destructor never called), the + semaphore count is never restored +- Eventually all semaphore counts are exhausted and notify() blocks/fails + +""" + +from pathlib import Path + +Import("env") # pylint: disable=undefined-variable + + +def _patch_ble_connection_header(source: Path) -> bool: + """ + Add _hvn_qsize member variable to BLEConnection class. + + This is needed to restore the semaphore to its correct count on disconnect. + + Returns True if patch was applied or already applied, False on error. + """ + try: + content = source.read_text() + + # Check if already patched + if "_hvn_qsize" in content: + return True # Already patched + + # Find the location to insert - after _phy declaration + original_pattern = ''' uint8_t _phy; + + uint8_t _role;''' + + patched_pattern = ''' uint8_t _phy; + uint8_t _hvn_qsize; + + uint8_t _role;''' + + if original_pattern not in content: + print("Bluefruit patch: WARNING - BLEConnection.h pattern not found") + return False + + content = content.replace(original_pattern, patched_pattern) + source.write_text(content) + + # Verify + if "_hvn_qsize" not in source.read_text(): + return False + + return True + except Exception as e: + print(f"Bluefruit patch: ERROR patching BLEConnection.h: {e}") + return False + + +def _patch_ble_connection_source(source: Path) -> bool: + """ + Patch BLEConnection.cpp to: + 1. Store hvn_qsize in constructor + 2. Restore _hvn_sem semaphore to full count on disconnect + + Returns True if patch was applied or already applied, False on error. + """ + try: + content = source.read_text() + + # Check if already patched (look for the restore loop) + if "uxSemaphoreGetCount(_hvn_sem)" in content: + return True # Already patched + + # Patch 1: Store queue size in constructor + constructor_original = ''' _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);''' + + constructor_patched = ''' _hvn_qsize = hvn_qsize; + _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);''' + + if constructor_original not in content: + print("Bluefruit patch: WARNING - BLEConnection.cpp constructor pattern not found") + return False + + content = content.replace(constructor_original, constructor_patched) + + # Patch 2: Restore semaphore on disconnect + disconnect_original = ''' case BLE_GAP_EVT_DISCONNECTED: + // mark as disconnected + _connected = false; + break;''' + + disconnect_patched = ''' case BLE_GAP_EVT_DISCONNECTED: + // Restore notification semaphore to full count + // This fixes lockup when disconnect occurs with notifications in flight + while (uxSemaphoreGetCount(_hvn_sem) < _hvn_qsize) { + xSemaphoreGive(_hvn_sem); + } + // Release indication semaphore if waiting + if (_hvc_sem) { + _hvc_received = false; + xSemaphoreGive(_hvc_sem); + } + // mark as disconnected + _connected = false; + break;''' + + if disconnect_original not in content: + print("Bluefruit patch: WARNING - BLEConnection.cpp disconnect pattern not found") + return False + + content = content.replace(disconnect_original, disconnect_patched) + source.write_text(content) + + # Verify + verify_content = source.read_text() + if "uxSemaphoreGetCount(_hvn_sem)" not in verify_content: + return False + if "_hvn_qsize = hvn_qsize" not in verify_content: + return False + + return True + except Exception as e: + print(f"Bluefruit patch: ERROR patching BLEConnection.cpp: {e}") + return False + + +def _apply_bluefruit_patches(target, source, env): # pylint: disable=unused-argument + framework_path = env.get("PLATFORMFW_DIR") + if not framework_path: + framework_path = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52") + + if not framework_path: + print("Bluefruit patch: ERROR - framework directory not found") + env.Exit(1) + return + + framework_dir = Path(framework_path) + bluefruit_lib = framework_dir / "libraries" / "Bluefruit52Lib" / "src" + patch_failed = False + + # Patch BLEConnection.h + conn_header = bluefruit_lib / "BLEConnection.h" + if conn_header.exists(): + before = conn_header.read_text() + success = _patch_ble_connection_header(conn_header) + after = conn_header.read_text() + + if success: + if before != after: + print("Bluefruit patch: OK - Applied BLEConnection.h fix (added _hvn_qsize member)") + else: + print("Bluefruit patch: OK - BLEConnection.h already patched") + else: + print("Bluefruit patch: FAILED - BLEConnection.h") + patch_failed = True + else: + print(f"Bluefruit patch: ERROR - BLEConnection.h not found at {conn_header}") + patch_failed = True + + # Patch BLEConnection.cpp + conn_source = bluefruit_lib / "BLEConnection.cpp" + if conn_source.exists(): + before = conn_source.read_text() + success = _patch_ble_connection_source(conn_source) + after = conn_source.read_text() + + if success: + if before != after: + print("Bluefruit patch: OK - Applied BLEConnection.cpp fix (restore semaphore on disconnect)") + else: + print("Bluefruit patch: OK - BLEConnection.cpp already patched") + else: + print("Bluefruit patch: FAILED - BLEConnection.cpp") + patch_failed = True + else: + print(f"Bluefruit patch: ERROR - BLEConnection.cpp not found at {conn_source}") + patch_failed = True + + if patch_failed: + print("Bluefruit patch: CRITICAL - Patch failed! Build aborted.") + env.Exit(1) + + +# Register the patch to run before build +bluefruit_action = env.VerboseAction(_apply_bluefruit_patches, "Applying Bluefruit BLE patches...") +env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", bluefruit_action) + +# Also run immediately to patch before any compilation +_apply_bluefruit_patches(None, None, env) diff --git a/platformio.ini b/platformio.ini index 3907cf64..75d37e86 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,7 +79,9 @@ extends = arduino_base platform = nordicnrf52 platform_packages = framework-arduinoadafruitnrf52 @ 1.10700.0 -extra_scripts = create-uf2.py +extra_scripts = + create-uf2.py + arch/nrf52/extra_scripts/patch_bluefruit.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM -D LFS_NO_ASSERT=1 From 6db57677f96e9f2ca0e7f43ada723adbfd030b3c Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Thu, 4 Dec 2025 12:01:00 +0100 Subject: [PATCH 31/42] tracker_l1: enable dc/dc converter --- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index c5c9db65..34d9a874 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -23,6 +23,15 @@ void WioTrackerL1Board::begin() { startup_reason = BD_STARTUP_NORMAL; btn_prev_state = HIGH; + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } + pinMode(PIN_VBAT_READ, INPUT); // VBAT ADC input // Set all button pins to INPUT_PULLUP pinMode(PIN_BUTTON1, INPUT_PULLUP); From 73ab0d881361c215b01e34cae9d3ba8064bef576 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:48:01 +0100 Subject: [PATCH 32/42] Improve SerialBLEInterface --- src/helpers/nrf52/SerialBLEInterface.cpp | 376 ++++++++++++++++------- src/helpers/nrf52/SerialBLEInterface.h | 32 +- 2 files changed, 289 insertions(+), 119 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index dbe6f393..b4811a20 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -1,193 +1,353 @@ #include "SerialBLEInterface.h" +#include +#include +#include "ble_gap.h" +#include "ble_hci.h" -static SerialBLEInterface* instance; +#define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds + +static SerialBLEInterface* instance = nullptr; void SerialBLEInterface::onConnect(uint16_t connection_handle) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: connected"); - // we now set _isDeviceConnected=true in onSecured callback instead + BLE_DEBUG_PRINTLN("SerialBLEInterface: connected handle=0x%04X", connection_handle); + if (instance) { + instance->_conn_handle = connection_handle; + instance->_isDeviceConnected = false; + instance->clearBuffers(); + } } void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected reason=%d", reason); - if(instance){ - instance->_isDeviceConnected = false; - instance->startAdv(); + BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected handle=0x%04X reason=%u", connection_handle, reason); + if (instance) { + if (instance->_conn_handle == connection_handle) { + instance->_conn_handle = BLE_CONN_HANDLE_INVALID; + instance->_isDeviceConnected = false; + instance->clearBuffers(); + } } } void SerialBLEInterface::onSecured(uint16_t connection_handle) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured"); - if(instance){ - instance->_isDeviceConnected = true; - // no need to stop advertising on connect, as the ble stack does this automatically + BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured handle=0x%04X", connection_handle); + if (instance) { + if (instance->isValidConnection(connection_handle, true)) { + instance->_isDeviceConnected = true; + + // Connection interval units: 1.25ms, supervision timeout units: 10ms + // Apple: "The product will not read or use the parameters in the Peripheral Preferred Connection Parameters characteristic." + // So we explicitly set it here to make Android & Apple match + ble_gap_conn_params_t conn_params; + conn_params.min_conn_interval = 12; // 15ms + conn_params.max_conn_interval = 24; // 30ms + conn_params.slave_latency = 0; + conn_params.conn_sup_timeout = 200; // 2000ms + + uint32_t err_code = sd_ble_gap_conn_param_update(connection_handle, &conn_params); + if (err_code == NRF_SUCCESS) { + BLE_DEBUG_PRINTLN("Connection parameter update requested: 15-30ms interval, 2s timeout"); + } else { + BLE_DEBUG_PRINTLN("Failed to request connection parameter update: %lu", err_code); + } + } else { + BLE_DEBUG_PRINTLN("onSecured: ignoring stale/duplicate callback"); + } + } +} + +bool SerialBLEInterface::onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request) { + (void)connection_handle; + (void)passkey; + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing passkey request match=%d", match_request); + return true; +} + +void SerialBLEInterface::onPairingComplete(uint16_t connection_handle, uint8_t auth_status) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing complete handle=0x%04X status=%u", connection_handle, auth_status); + if (instance) { + if (instance->isValidConnection(connection_handle)) { + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing successful"); + } else { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing failed, disconnecting"); + instance->disconnect(); + } + } else { + BLE_DEBUG_PRINTLN("onPairingComplete: ignoring stale callback"); + } + } +} + +void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) { + if (!instance) return; + + if (evt->header.evt_id == BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST) { + uint16_t conn_handle = evt->evt.gap_evt.conn_handle; + if (instance->isValidConnection(conn_handle)) { + BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: handle=0x%04X, min_interval=%u, max_interval=%u, latency=%u, timeout=%u", + conn_handle, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.min_conn_interval, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.max_conn_interval, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.slave_latency, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.conn_sup_timeout); + + uint32_t err_code = sd_ble_gap_conn_param_update(conn_handle, NULL); + if (err_code == NRF_SUCCESS) { + BLE_DEBUG_PRINTLN("Accepted CONN_PARAM_UPDATE_REQUEST (using PPCP)"); + } else { + BLE_DEBUG_PRINTLN("ERROR: Failed to accept CONN_PARAM_UPDATE_REQUEST: 0x%08X", err_code); + } + } else { + BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: ignoring stale callback for handle=0x%04X", conn_handle); + } } } void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { - instance = this; char charpin[20]; - sprintf(charpin, "%d", pin_code); - + snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code); + + // If we want to control BLE LED ourselves, uncomment this: + // Bluefruit.autoConnLed(false); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(250, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); // increase MTU - Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.begin(); + + // Connection interval units: 1.25ms, supervision timeout units: 10ms + ble_gap_conn_params_t ppcp_params; + ppcp_params.min_conn_interval = 12; // 15ms + ppcp_params.max_conn_interval = 24; // 30ms + ppcp_params.slave_latency = 0; + ppcp_params.conn_sup_timeout = 200; // 2000ms + + uint32_t err_code = sd_ble_gap_ppcp_set(&ppcp_params); + if (err_code == NRF_SUCCESS) { + BLE_DEBUG_PRINTLN("PPCP set: 15-30ms interval, 2s timeout"); + } else { + BLE_DEBUG_PRINTLN("Failed to set PPCP: %lu", err_code); + } + + Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.setName(device_name); Bluefruit.Security.setMITM(true); Bluefruit.Security.setPIN(charpin); + Bluefruit.Security.setIOCaps(true, false, false); + Bluefruit.Security.setPairPasskeyCallback(onPairingPasskey); + Bluefruit.Security.setPairCompleteCallback(onPairingComplete); Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); Bluefruit.Security.setSecuredCallback(onSecured); - // To be consistent OTA DFU should be added first if it exists - //bledfu.begin(); + Bluefruit.setEventCallback(onBLEEvent); - // Configure and start the BLE Uart service bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bleuart.begin(); - -} + bleuart.setRxCallback(onBleUartRX); -void SerialBLEInterface::startAdv() { - - BLE_DEBUG_PRINTLN("SerialBLEInterface: starting advertising"); - - // clean restart if already advertising - if(Bluefruit.Advertising.isRunning()){ - BLE_DEBUG_PRINTLN("SerialBLEInterface: already advertising, stopping to allow clean restart"); - Bluefruit.Advertising.stop(); - } - - Bluefruit.Advertising.clearData(); // clear advertising data - Bluefruit.ScanResponse.clearData(); // clear scan response data - - // Advertising packet Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower(); - - // Include the BLE UART (AKA 'NUS') 128-bit UUID Bluefruit.Advertising.addService(bleuart); - // Secondary Scan Response packet (optional) - // Since there is no room for 'Name' in Advertising packet Bluefruit.ScanResponse.addName(); - /* Start Advertising - * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms - * - Timeout for fast mode is 30 seconds - * - Start(timeout) with timeout = 0 will advertise forever (until connected) - * - * For recommended advertising interval - * https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect Bluefruit.Advertising.setInterval(32, 244); - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + Bluefruit.Advertising.setFastTimeout(30); + + Bluefruit.Advertising.restartOnDisconnect(true); } -void SerialBLEInterface::stopAdv() { +void SerialBLEInterface::clearBuffers() { + send_queue_len = 0; + recv_queue_len = 0; + bleuart.flush(); +} - BLE_DEBUG_PRINTLN("SerialBLEInterface: stopping advertising"); - - // we only want to stop advertising if it's running, otherwise an invalid state error is logged by ble stack - if(!Bluefruit.Advertising.isRunning()){ - return; +void SerialBLEInterface::shiftSendQueueLeft() { + if (send_queue_len > 0) { + send_queue_len--; + for (uint8_t i = 0; i < send_queue_len; i++) { + send_queue[i] = send_queue[i + 1]; + } } - - // stop advertising - Bluefruit.Advertising.stop(); - } -// ---------- public methods +void SerialBLEInterface::shiftRecvQueueLeft() { + if (recv_queue_len > 0) { + recv_queue_len--; + for (uint8_t i = 0; i < recv_queue_len; i++) { + recv_queue[i] = recv_queue[i + 1]; + } + } +} -void SerialBLEInterface::enable() { +bool SerialBLEInterface::isValidConnection(uint16_t handle, bool requireWaitingForSecurity) const { + if (_conn_handle != handle) { + return false; + } + BLEConnection* conn = Bluefruit.Connection(handle); + if (conn == nullptr || !conn->connected()) { + return false; + } + if (requireWaitingForSecurity && _isDeviceConnected) { + return false; + } + return true; +} + +bool SerialBLEInterface::isAdvertising() const { + ble_gap_addr_t adv_addr; + uint32_t err_code = sd_ble_gap_adv_addr_get(0, &adv_addr); + return (err_code == NRF_SUCCESS); +} + +void SerialBLEInterface::enable() { if (_isEnabled) return; _isEnabled = true; clearBuffers(); + _last_health_check = millis(); - // Start advertising - startAdv(); + Bluefruit.Advertising.start(0); +} + +void SerialBLEInterface::disconnect() { + if (_conn_handle != BLE_CONN_HANDLE_INVALID) { + sd_ble_gap_disconnect(_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); + } } void SerialBLEInterface::disable() { _isEnabled = false; - BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); + BLE_DEBUG_PRINTLN("SerialBLEInterface: disable"); -#ifdef RAK_BOARD - Bluefruit.disconnect(Bluefruit.connHandle()); -#else - uint16_t conn_id; - if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) { - Bluefruit.disconnect(conn_id); - } -#endif - - Bluefruit.Advertising.restartOnDisconnect(false); + disconnect(); Bluefruit.Advertising.stop(); - Bluefruit.Advertising.clearData(); - - stopAdv(); + _last_health_check = 0; } size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { if (len > MAX_FRAME_SIZE) { - BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d", len); + BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%u", (unsigned)len); return 0; } - if (_isDeviceConnected && len > 0) { + bool connected = isConnected(); + if (connected && len > 0) { if (send_queue_len >= FRAME_QUEUE_SIZE) { BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!"); return 0; } - send_queue[send_queue_len].len = len; // add to send queue + send_queue[send_queue_len].len = len; memcpy(send_queue[send_queue_len].buf, src, len); send_queue_len++; - + return len; } return 0; } -#define BLE_WRITE_MIN_INTERVAL 60 - -bool SerialBLEInterface::isWriteBusy() const { - return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write? -} - size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { - if (send_queue_len > 0 // first, check send queue - && millis() >= _last_write + BLE_WRITE_MIN_INTERVAL // space the writes apart - ) { - _last_write = millis(); - bleuart.write(send_queue[0].buf, send_queue[0].len); - BLE_DEBUG_PRINTLN("writeBytes: sz=%d, hdr=%d", (uint32_t)send_queue[0].len, (uint32_t) send_queue[0].buf[0]); - - send_queue_len--; - for (int i = 0; i < send_queue_len; i++) { // delete top item from queue - send_queue[i] = send_queue[i + 1]; - } - } else { - int len = bleuart.available(); - if (len > 0) { - bleuart.readBytes(dest, len); - BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]); - return len; + if (send_queue_len > 0) { + if (!isConnected()) { + BLE_DEBUG_PRINTLN("writeBytes: connection invalid, clearing send queue"); + send_queue_len = 0; + } else { + Frame frame_to_send = send_queue[0]; + + size_t written = bleuart.write(frame_to_send.buf, frame_to_send.len); + if (written == frame_to_send.len) { + BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]); + shiftSendQueueLeft(); + } else if (written > 0) { + BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len); + shiftSendQueueLeft(); + } else { + if (!isConnected()) { + BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame"); + shiftSendQueueLeft(); + } else { + BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry"); + } + } } } + + if (recv_queue_len > 0) { + size_t len = recv_queue[0].len; + memcpy(dest, recv_queue[0].buf, len); + + BLE_DEBUG_PRINTLN("readBytes: sz=%u, hdr=%u", (unsigned)len, (unsigned)dest[0]); + + shiftRecvQueueLeft(); + return len; + } + + // Advertising watchdog: periodically check if advertising is running, restart if not + // Only run when truly disconnected (no connection handle), not during connection establishment + unsigned long now = millis(); + if (_isEnabled && !isConnected() && _conn_handle == BLE_CONN_HANDLE_INVALID) { + if (now - _last_health_check >= BLE_HEALTH_CHECK_INTERVAL) { + _last_health_check = now; + + if (!isAdvertising()) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: advertising watchdog - advertising stopped, restarting"); + Bluefruit.Advertising.start(0); + } + } + } + return 0; } -bool SerialBLEInterface::isConnected() const { - return _isDeviceConnected; +void SerialBLEInterface::onBleUartRX(uint16_t conn_handle) { + if (!instance) { + return; + } + + if (instance->_conn_handle != conn_handle || !instance->isConnected()) { + while (instance->bleuart.available() > 0) { + instance->bleuart.read(); + } + return; + } + + while (instance->bleuart.available() > 0) { + if (instance->recv_queue_len >= FRAME_QUEUE_SIZE) { + while (instance->bleuart.available() > 0) { + instance->bleuart.read(); + } + BLE_DEBUG_PRINTLN("onBleUartRX: recv queue full, dropping data"); + break; + } + + int avail = instance->bleuart.available(); + + if (avail > MAX_FRAME_SIZE) { + BLE_DEBUG_PRINTLN("onBleUartRX: WARN: BLE RX overflow, avail=%d, draining all", avail); + uint8_t drain_buf[32]; + while (instance->bleuart.available() > 0) { + int chunk = instance->bleuart.available() > 32 ? 32 : instance->bleuart.available(); + instance->bleuart.readBytes(drain_buf, chunk); + } + continue; + } + + int read_len = avail; + instance->recv_queue[instance->recv_queue_len].len = read_len; + instance->bleuart.readBytes(instance->recv_queue[instance->recv_queue_len].buf, read_len); + instance->recv_queue_len++; + } +} + +bool SerialBLEInterface::isConnected() const { + return _isDeviceConnected && Bluefruit.connected() > 0; +} + +bool SerialBLEInterface::isWriteBusy() const { + return send_queue_len >= (FRAME_QUEUE_SIZE - 1); } diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index bf29892d..557b86c5 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -11,41 +11,51 @@ class SerialBLEInterface : public BaseSerialInterface { BLEUart bleuart; bool _isEnabled; bool _isDeviceConnected; - unsigned long _last_write; + uint16_t _conn_handle; + unsigned long _last_health_check; struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; }; - #define FRAME_QUEUE_SIZE 4 - int send_queue_len; + #define FRAME_QUEUE_SIZE 12 + + uint8_t send_queue_len; Frame send_queue[FRAME_QUEUE_SIZE]; + + uint8_t recv_queue_len; + Frame recv_queue[FRAME_QUEUE_SIZE]; - void clearBuffers() { send_queue_len = 0; } + void clearBuffers(); + void shiftSendQueueLeft(); + void shiftRecvQueueLeft(); + bool isValidConnection(uint16_t handle, bool requireWaitingForSecurity = false) const; + bool isAdvertising() const; static void onConnect(uint16_t connection_handle); static void onDisconnect(uint16_t connection_handle, uint8_t reason); static void onSecured(uint16_t connection_handle); + static bool onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request); + static void onPairingComplete(uint16_t connection_handle, uint8_t auth_status); + static void onBLEEvent(ble_evt_t* evt); + static void onBleUartRX(uint16_t conn_handle); public: SerialBLEInterface() { _isEnabled = false; _isDeviceConnected = false; - _last_write = 0; + _conn_handle = BLE_CONN_HANDLE_INVALID; + _last_health_check = 0; send_queue_len = 0; + recv_queue_len = 0; } - void startAdv(); - void stopAdv(); void begin(const char* device_name, uint32_t pin_code); - - // BaseSerialInterface methods + void disconnect(); void enable() override; void disable() override; bool isEnabled() const override { return _isEnabled; } - bool isConnected() const override; - bool isWriteBusy() const override; size_t writeFrame(const uint8_t src[], size_t len) override; size_t checkRecvFrame(uint8_t dest[]) override; From 10b43a8f9fa2f1868b2149bdff2d45207b204500 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Fri, 5 Dec 2025 12:39:37 +0100 Subject: [PATCH 33/42] variants: XIAO NRF52: Enable button pullup Some versions of the Wio-SX1262 board don't have the button and the pullup resistor populated. Enable the internal pullup to prevent a floating pin and spurious button presses on those boards. This fixes #1173. Signed-off-by: Frieder Schrempf --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 69218926..f847c654 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -38,7 +38,7 @@ void XiaoNrf52Board::begin() { digitalWrite(VBAT_ENABLE, HIGH); #ifdef PIN_USER_BTN - pinMode(PIN_USER_BTN, INPUT); + pinMode(PIN_USER_BTN, INPUT_PULLUP); #endif #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) From d834d66803cee513de9f381ecbe6014c67a93d65 Mon Sep 17 00:00:00 2001 From: Christophe Vanlancker Date: Fri, 5 Dec 2025 20:44:56 +0100 Subject: [PATCH 34/42] feat(tdeck): enable GPS support and configure pins --- variants/lilygo_tdeck/platformio.ini | 19 +++++++++++++++++++ variants/lilygo_tdeck/target.cpp | 4 +++- variants/lilygo_tdeck/target.h | 4 +++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tdeck/platformio.ini b/variants/lilygo_tdeck/platformio.ini index adfe9d1e..807663f8 100644 --- a/variants/lilygo_tdeck/platformio.ini +++ b/variants/lilygo_tdeck/platformio.ini @@ -19,6 +19,21 @@ build_flags = -D SX126X_RX_BOOSTED_GAIN=1 -D SX126X_DIO3_TCXO_VOLTAGE=1.8f -D P_LORA_DIO_1=45 ; LORA IRQ pin + -D ENV_INCLUDE_GPS=1 + -D ENV_INCLUDE_AHTX0=0 + -D ENV_INCLUDE_BME280=0 + -D ENV_INCLUDE_BMP280=0 + -D ENV_INCLUDE_SHTC3=0 + -D ENV_INCLUDE_SHT4X=0 + -D ENV_INCLUDE_LPS22HB=0 + -D ENV_INCLUDE_INA3221=0 + -D ENV_INCLUDE_INA219=0 + -D ENV_INCLUDE_INA226=0 + -D ENV_INCLUDE_INA260=0 + -D ENV_INCLUDE_MLX90614=0 + -D ENV_INCLUDE_VL53L0X=0 + -D ENV_INCLUDE_BME680=0 + -D ENV_INCLUDE_BMP085=0 -D P_LORA_NSS=9 ; LORA SS pin -D P_LORA_RESET=17 ; LORA RST pin -D P_LORA_BUSY=13 ; LORA Busy pin @@ -35,8 +50,12 @@ build_flags = -D PIN_TFT_DC=11 -D PIN_TFT_SCL=40 -D PIN_TFT_SDA=41 + -D PIN_GPS_RX=43 + -D PIN_GPS_TX=44 + -D GPS_BAUD_RATE=38400 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tdeck> + + lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} diff --git a/variants/lilygo_tdeck/target.cpp b/variants/lilygo_tdeck/target.cpp index 1120b3ad..50ffa735 100644 --- a/variants/lilygo_tdeck/target.cpp +++ b/variants/lilygo_tdeck/target.cpp @@ -14,7 +14,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +MicroNMEALocationProvider gps(Serial1, &rtc_clock); +EnvironmentSensorManager sensors(gps); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -24,6 +25,7 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); + Wire.begin(18, 8); #if defined(P_LORA_SCLK) return radio.std_init(&spi); diff --git a/variants/lilygo_tdeck/target.h b/variants/lilygo_tdeck/target.h index c803bf2c..4640925f 100644 --- a/variants/lilygo_tdeck/target.h +++ b/variants/lilygo_tdeck/target.h @@ -11,11 +11,13 @@ #include #include #endif +#include "helpers/sensors/EnvironmentSensorManager.h" +#include "helpers/sensors/MicroNMEALocationProvider.h" extern TDeckBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 01eb8716aff657171719c3102e9ba0efdd3eba16 Mon Sep 17 00:00:00 2001 From: Christophe Vanlancker Date: Fri, 5 Dec 2025 20:45:10 +0100 Subject: [PATCH 35/42] fix(core): optimize GPS loop and add display GPIO safeguards --- .../sensors/EnvironmentSensorManager.cpp | 2 +- src/helpers/ui/ST7789LCDDisplay.cpp | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b072bcb0..af29bb99 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -695,8 +695,8 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS + _location->loop(); if (millis() > next_gps_update) { - _location->loop(); if(gps_active){ #ifdef RAK_WISBLOCK_GPS diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 87f9b8ad..97d82f42 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -23,9 +23,13 @@ bool ST7789LCDDisplay::begin() { if (!_isOn) { if (_peripher_power) _peripher_power->claim(); - pinMode(PIN_TFT_LEDA_CTL, OUTPUT); - digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - digitalWrite(PIN_TFT_RST, HIGH); + if (PIN_TFT_LEDA_CTL != -1) { + pinMode(PIN_TFT_LEDA_CTL, OUTPUT); + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + } + if (PIN_TFT_RST != -1) { + digitalWrite(PIN_TFT_RST, HIGH); + } // Im not sure if this is just a t-deck problem or not, if your display is slow try this. #ifdef LILYGO_TDECK @@ -54,9 +58,15 @@ void ST7789LCDDisplay::turnOn() { void ST7789LCDDisplay::turnOff() { if (_isOn) { - digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - digitalWrite(PIN_TFT_RST, LOW); - digitalWrite(PIN_TFT_LEDA_CTL, LOW); + if (PIN_TFT_LEDA_CTL != -1) { + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + } + if (PIN_TFT_RST != -1) { + digitalWrite(PIN_TFT_RST, LOW); + } + if (PIN_TFT_LEDA_CTL != -1) { + digitalWrite(PIN_TFT_LEDA_CTL, LOW); + } _isOn = false; if (_peripher_power) _peripher_power->release(); From 638f41d14399ac772aaece7340775cc927175153 Mon Sep 17 00:00:00 2001 From: taco Date: Sat, 6 Dec 2025 16:21:17 +1100 Subject: [PATCH 36/42] calculate shared_secret on demand --- examples/companion_radio/MyMesh.cpp | 2 +- src/helpers/BaseChatMesh.cpp | 24 ++++++++++++++++++------ src/helpers/BaseChatMesh.h | 1 + src/helpers/ContactInfo.h | 3 ++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3aed2da7..9cbb2eba 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1238,7 +1238,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (_store->saveMainIdentity(identity)) { self_id = identity; writeOKFrame(); - // re-load contacts, to recalc shared secrets + // re-load contacts, to invalidate ecdh shared_secrets resetContacts(); _store->loadContacts(this); } else { diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 4ab3e03b..2855c625 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -113,8 +113,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, from->gps_lon = 0; from->sync_since = 0; - // only need to calculate the shared_secret once, for better performance - self_id.calcSharedSecret(from->shared_secret, id); + from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand } else { MESH_DEBUG_PRINTLN("onAdvertRecv: contacts table is full!"); return; @@ -147,7 +146,7 @@ int BaseChatMesh::searchPeersByHash(const uint8_t* hash) { void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; if (i >= 0 && i < num_contacts) { - // lookup pre-calculated shared_secret + ensureSharedSecretIsValid(contacts[i]); memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); @@ -293,6 +292,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) { // NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY) // override this method in various firmwares, if there's a better strategy + ensureSharedSecretIsValid(contact); mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0); if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay } @@ -342,6 +342,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3 temp[len++] = attempt; // hide attempt number at tail end of payload } + ensureSharedSecretIsValid(recipient); return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, len); } @@ -373,6 +374,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); memcpy(&temp[5], text, text_len + 1); + ensureSharedSecretIsValid(recipient); auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len); if (pkt == NULL) return MSG_SEND_FAILED; @@ -462,6 +464,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, tlen = 4 + len; } + ensureSharedSecretIsValid(recipient); pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen); } if (pkt) { @@ -489,6 +492,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); + ensureSharedSecretIsValid(recipient); pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, 4 + data_len); } if (pkt) { @@ -516,6 +520,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique + ensureSharedSecretIsValid(recipient); pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp)); } if (pkt) { @@ -639,6 +644,7 @@ void BaseChatMesh::checkConnections() { // calc expected ACK reply mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE); + ensureSharedSecretIsValid(*contact); auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9); if (pkt) { sendDirect(pkt, contact->out_path, contact->out_path_len); @@ -703,14 +709,20 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { auto dest = &contacts[num_contacts++]; *dest = contact; - // calc the ECDH shared secret (just once for performance) - self_id.calcSharedSecret(dest->shared_secret, contact.id); - + dest->shared_secret_valid = false; // mark shared_secret as needing calculation return true; // success } return false; } +void BaseChatMesh::ensureSharedSecretIsValid(const ContactInfo& contact) { + if (contact.shared_secret_valid) { + return; // already calculated + } + self_id.calcSharedSecret(contact.shared_secret, contact.id); + contact.shared_secret_valid = true; +} + bool BaseChatMesh::removeContact(ContactInfo& contact) { int idx = 0; while (idx < num_contacts && !contacts[idx].id.matches(contact.id)) { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 76b0dd1c..105d2a79 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -73,6 +73,7 @@ class BaseChatMesh : public mesh::Mesh { mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); + void ensureSharedSecretIsValid(const ContactInfo& contact); protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index 4a8038d3..b0b54aef 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -9,9 +9,10 @@ struct ContactInfo { uint8_t type; // on of ADV_TYPE_* uint8_t flags; int8_t out_path_len; + mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock - uint8_t shared_secret[PUB_KEY_SIZE]; + mutable uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t lastmod; // by OUR clock int32_t gps_lat, gps_lon; // 6 dec places uint32_t sync_since; From d7adcc136b2ad9e4247955854d2451bb16863d47 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 6 Dec 2025 16:49:25 +1100 Subject: [PATCH 37/42] * LPPDataHelpers, readCurrent() signed value --- src/helpers/sensors/LPPDataHelpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/LPPDataHelpers.h b/src/helpers/sensors/LPPDataHelpers.h index b9025de4..37b50f3f 100644 --- a/src/helpers/sensors/LPPDataHelpers.h +++ b/src/helpers/sensors/LPPDataHelpers.h @@ -113,7 +113,7 @@ public: return _pos <= _len; } bool readCurrent(float& amps) { - amps = getFloat(&_buf[_pos], 2, 1000, false); _pos += 2; + amps = getFloat(&_buf[_pos], 2, 1000, true); _pos += 2; return _pos <= _len; } bool readPower(float& watts) { From 676c317f78df09f2f5fed9b499c25e0aa015e722 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 6 Dec 2025 19:17:45 +1100 Subject: [PATCH 38/42] * refactor: on-demand getSharedSecret() --- src/helpers/BaseChatMesh.cpp | 32 ++++++++------------------------ src/helpers/BaseChatMesh.h | 1 - src/helpers/ContactInfo.h | 12 +++++++++++- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 2855c625..597444fa 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -146,8 +146,7 @@ int BaseChatMesh::searchPeersByHash(const uint8_t* hash) { void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; if (i >= 0 && i < num_contacts) { - ensureSharedSecretIsValid(contacts[i]); - memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE); + memcpy(dest_secret, contacts[i].getSharedSecret(self_id), PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); } @@ -292,8 +291,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) { // NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY) // override this method in various firmwares, if there's a better strategy - ensureSharedSecretIsValid(contact); - mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0); + mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0); if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay } @@ -342,8 +340,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3 temp[len++] = attempt; // hide attempt number at tail end of payload } - ensureSharedSecretIsValid(recipient); - return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, len); + return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len); } int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) { @@ -374,8 +371,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); memcpy(&temp[5], text, text_len + 1); - ensureSharedSecretIsValid(recipient); - auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len); + auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len); if (pkt == NULL) return MSG_SEND_FAILED; uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -464,8 +460,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, tlen = 4 + len; } - ensureSharedSecretIsValid(recipient); - pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen); + pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, tlen); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -492,8 +487,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); - ensureSharedSecretIsValid(recipient); - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, 4 + data_len); + pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -520,8 +514,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique - ensureSharedSecretIsValid(recipient); - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp)); + pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp)); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -644,8 +637,7 @@ void BaseChatMesh::checkConnections() { // calc expected ACK reply mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE); - ensureSharedSecretIsValid(*contact); - auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9); + auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9); if (pkt) { sendDirect(pkt, contact->out_path, contact->out_path_len); } @@ -715,14 +707,6 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { return false; } -void BaseChatMesh::ensureSharedSecretIsValid(const ContactInfo& contact) { - if (contact.shared_secret_valid) { - return; // already calculated - } - self_id.calcSharedSecret(contact.shared_secret, contact.id); - contact.shared_secret_valid = true; -} - bool BaseChatMesh::removeContact(ContactInfo& contact) { int idx = 0; while (idx < num_contacts && !contacts[idx].id.matches(contact.id)) { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 105d2a79..76b0dd1c 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -73,7 +73,6 @@ class BaseChatMesh : public mesh::Mesh { mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); - void ensureSharedSecretIsValid(const ContactInfo& contact); protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index b0b54aef..eff07741 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -12,8 +12,18 @@ struct ContactInfo { mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock - mutable uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t lastmod; // by OUR clock int32_t gps_lat, gps_lon; // 6 dec places uint32_t sync_since; + + const uint8_t* getSharedSecret(const mesh::LocalIdentity& self_id) const { + if (!shared_secret_valid) { + self_id.calcSharedSecret(shared_secret, id.pub_key); + shared_secret_valid = true; + } + return shared_secret; + } + +private: + mutable uint8_t shared_secret[PUB_KEY_SIZE]; }; From b91b854a1dc5b24c772748bc701c9b08eb7e9e0c Mon Sep 17 00:00:00 2001 From: agessaman Date: Mon, 8 Dec 2025 19:53:33 -0800 Subject: [PATCH 39/42] fix output from LPS22HB: convert barometric pressure from kPa to hPa in EnvironmentSensorManager --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index af29bb99..2692ec9c 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -399,7 +399,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen #if ENV_INCLUDE_LPS22HB if (LPS22HB_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure() * 10); // convert kPa to hPa } #endif From 14efaf6fd3a2ffd698134e37b4c983c87a50b683 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 29 Nov 2025 10:55:01 +0100 Subject: [PATCH 40/42] thinknode_m6: initial port --- boards/thinknode_m6.json | 72 +++++++++++++ variants/thinknode_m5/platformio.ini | 2 +- variants/thinknode_m6/ThinkNodeM6Board.cpp | 95 ++++++++++++++++ variants/thinknode_m6/ThinkNodeM6Board.h | 56 ++++++++++ variants/thinknode_m6/platformio.ini | 120 +++++++++++++++++++++ variants/thinknode_m6/target.cpp | 49 +++++++++ variants/thinknode_m6/target.h | 31 ++++++ variants/thinknode_m6/variant.cpp | 35 ++++++ variants/thinknode_m6/variant.h | 108 +++++++++++++++++++ 9 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 boards/thinknode_m6.json create mode 100644 variants/thinknode_m6/ThinkNodeM6Board.cpp create mode 100644 variants/thinknode_m6/ThinkNodeM6Board.h create mode 100644 variants/thinknode_m6/platformio.ini create mode 100644 variants/thinknode_m6/target.cpp create mode 100644 variants/thinknode_m6/target.h create mode 100644 variants/thinknode_m6/variant.cpp create mode 100644 variants/thinknode_m6/variant.h diff --git a/boards/thinknode_m6.json b/boards/thinknode_m6.json new file mode 100644 index 00000000..1f91b9aa --- /dev/null +++ b/boards/thinknode_m6.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ] + ], + "usb_product": "elecrow_solar", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M6", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "elecrow solar", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ] + }, + "url": "https://github.com/Elecrow-RD", + "vendor": "ELECROW" +} diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 23db506a..cf9c9d41 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,7 +3,7 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 - -I src/helpres/sensors + -I src/helpers/sensors -D THINKNODE_M5 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 diff --git a/variants/thinknode_m6/ThinkNodeM6Board.cpp b/variants/thinknode_m6/ThinkNodeM6Board.cpp new file mode 100644 index 00000000..1ccc2026 --- /dev/null +++ b/variants/thinknode_m6/ThinkNodeM6Board.cpp @@ -0,0 +1,95 @@ +#include "ThinkNodeM6Board.h" +#include + +#ifdef THINKNODE_M6 + +#include +#include + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void ThinkNodeM6Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, LOW); +#endif + + delay(10); // give sx1262 some time to power up +} + +uint16_t ThinkNodeM6Board::getBattMilliVolts() { + int adcvalue = 0; + + digitalWrite(PIN_ADC_CTRL, HIGH); + analogReference(AR_INTERNAL_3_0); + analogReadResolution(12); + delay(10); + + // ADC range is 0..3000mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + digitalWrite(PIN_ADC_CTRL, LOW); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); +} + +bool ThinkNodeM6Board::startOTAUpdate(const char *id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("THINKNODE_M1_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} +#endif diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h new file mode 100644 index 00000000..c3d7dad6 --- /dev/null +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +// built-ins +#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 + +#define VBAT_DIVIDER_COMP ADC_MULTIPLIER // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ BATTERY_PIN +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class ThinkNodeM6Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + + void begin(); + uint16_t getBattMilliVolts() override; + bool startOTAUpdate(const char* id, char reply[]) override; + + uint8_t getStartupReason() const override { + return startup_reason; + } + + #if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } + #endif + + const char* getManufacturerName() const override { + return "Elecrow ThinkNode-M6"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + void powerOff() override { + + // turn off all leds, sd_power_system_off will not do this for us + #ifdef P_LORA_TX_LED + digitalWrite(P_LORA_TX_LED, LOW); + #endif + + // power off board + sd_power_system_off(); + + } +}; diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini new file mode 100644 index 00000000..16394ced --- /dev/null +++ b/variants/thinknode_m6/platformio.ini @@ -0,0 +1,120 @@ +[ThinkNode_M6] +extends = nrf52_base +board = thinknode_m6 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/thinknode_m6 + -D THINKNODE_M6=1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=38 + -D P_LORA_NSS=44 + -D P_LORA_RESET=42 + -D P_LORA_BUSY=43 + -D P_LORA_SCLK=45 + -D P_LORA_MISO=47 + -D P_LORA_MOSI=46 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=PIN_LED_BLUE +; -D PERSISTANT_GPS=1 +; -D ENV_SKIP_GPS_DETECT=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + + + + + +<../variants/thinknode_m6> +lib_deps = + ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} +debug_tool = jlink +upload_protocol = nrfutil + +[env:ThinkNode_M6_repeater] +extends = ThinkNode_M6 +build_flags = + ${ThinkNode_M6.build_flags} + -D ADVERT_NAME='"ThinkNode Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 + -D GPS_NMEA_DEBUG=1 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<../examples/simple_repeater/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + +[env:ThinkNode_M6_room_server] +extends = ThinkNode_M6 +build_flags = + ${ThinkNode_M6.build_flags} + -D ADVERT_NAME='"ThinkNode Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<../examples/simple_room_server/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + +[env:ThinkNode_M6_companion_radio_ble] +extends = ThinkNode_M6 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${ThinkNode_M6.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D QSPIFLASH=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M6.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M6_companion_radio_usb] +extends = ThinkNode_M6 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${ThinkNode_M6.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D QSPIFLASH=1 + -D OFFLINE_QUEUE_SIZE=256 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +build_src_filter = ${ThinkNode_M6.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 \ No newline at end of file diff --git a/variants/thinknode_m6/target.cpp b/variants/thinknode_m6/target.cpp new file mode 100644 index 00000000..c14dd300 --- /dev/null +++ b/variants/thinknode_m6/target.cpp @@ -0,0 +1,49 @@ +#include +#include "target.h" +#include +#include + +ThinkNodeM6Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/thinknode_m6/target.h b/variants/thinknode_m6/target.h new file mode 100644 index 00000000..38b1fed1 --- /dev/null +++ b/variants/thinknode_m6/target.h @@ -0,0 +1,31 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern ThinkNodeM6Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m6/variant.cpp b/variants/thinknode_m6/variant.cpp new file mode 100644 index 00000000..c88f387d --- /dev/null +++ b/variants/thinknode_m6/variant.cpp @@ -0,0 +1,35 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() { + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(QSPI_FLASH_EN, OUTPUT); + digitalWrite(QSPI_FLASH_EN, HIGH); + + // For now stick adc_ctrl to fixed value + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); + + pinMode(PIN_LED_RED, OUTPUT); + pinMode(PIN_LED_BLUE, OUTPUT); + digitalWrite(PIN_LED_BLUE, LOW); + digitalWrite(PIN_LED_RED, LOW); + + // gps + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, HIGH); + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, HIGH); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, HIGH); +} diff --git a/variants/thinknode_m6/variant.h b/variants/thinknode_m6/variant.h new file mode 100644 index 00000000..70fd6506 --- /dev/null +++ b/variants/thinknode_m6/variant.h @@ -0,0 +1,108 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) + +#define WIRE_INTERFACES_COUNT (1) +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define PIN_PWR_EN (27) + +#define BATTERY_PIN (28) +#define ADC_MULTIPLIER (1.75F) +#define PIN_ADC_CTRL (11) + +#define ADC_RESOLUTION (12) +#define BATTERY_SENSE_RES (12) + +#define AREF_VOLTAGE (3.0) + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +#define PIN_SERIAL2_RX (22) +#define PIN_SERIAL2_TX (24) + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define PIN_WIRE_SDA (41) // P1.9 +#define PIN_WIRE_SCL (8) // P0.8 + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (47) +#define PIN_SPI_MOSI (46) +#define PIN_SPI_SCK (45) +//#define PIN_SPI_NSS (24) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define PIN_LED_RED (12) +#define PIN_LED_BLUE (7) +#define LED_BLUE (-1) + +#define LED_BUILTIN PIN_LED_BLUE +#define PIN_LED LED_BUILTIN +#define LED_PIN LED_BUILTIN +#define LED_STATE_ON HIGH + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (17) +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN + +//////////////////////////////////////////////////////////////////////////////// +// QSPI + +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +#define PIN_QSPI_SCK (35) +#define PIN_QSPI_CS (23) +#define PIN_QSPI_IO0 (33) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (34) // MISO if using two bit interface +#define PIN_QSPI_IO2 (36) // WP if using two bit interface (i.e. not used) +#define PIN_QSPI_IO3 (37) // HOLD if using two bit interface (i.e. not used) +#define QSPI_FLASH_EN (21) + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define GPS_L76K +#define PIN_GPS_RX (2) +#define PIN_GPS_TX (3) +#define PIN_GPS_EN (6) // EN +#define PIN_GPS_RESET (29) +#define PIN_GPS_STANDBY (30) // STANDBY +#define PIN_GPS_PPS (31) +#define GPS_BAUD_RATE 9600 From bde4fc3a231f53a4ba95ca2a34ff23509ab60ac0 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 28 Nov 2025 22:04:24 +0100 Subject: [PATCH 41/42] thinknode_m3: initial commit --- boards/thinknode_m3.json | 72 ++++++++++++ variants/thinknode_m3/ThinknodeM3Board.cpp | 80 ++++++++++++++ variants/thinknode_m3/ThinknodeM3Board.h | 68 ++++++++++++ variants/thinknode_m3/platformio.ini | 122 +++++++++++++++++++++ variants/thinknode_m3/target.cpp | 99 +++++++++++++++++ variants/thinknode_m3/target.h | 29 +++++ variants/thinknode_m3/variant.cpp | 95 ++++++++++++++++ variants/thinknode_m3/variant.h | 109 ++++++++++++++++++ 8 files changed, 674 insertions(+) create mode 100644 boards/thinknode_m3.json create mode 100644 variants/thinknode_m3/ThinknodeM3Board.cpp create mode 100644 variants/thinknode_m3/ThinknodeM3Board.h create mode 100644 variants/thinknode_m3/platformio.ini create mode 100644 variants/thinknode_m3/target.cpp create mode 100644 variants/thinknode_m3/target.h create mode 100644 variants/thinknode_m3/variant.cpp create mode 100644 variants/thinknode_m3/variant.h diff --git a/boards/thinknode_m3.json b/boards/thinknode_m3.json new file mode 100644 index 00000000..617740b6 --- /dev/null +++ b/boards/thinknode_m3.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ] + ], + "usb_product": "elecrow_eink", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M3", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "elecrow nrf", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ] + }, + "url": "https://github.com/Elecrow-RD", + "vendor": "ELECROW" +} \ No newline at end of file diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinknodeM3Board.cpp new file mode 100644 index 00000000..74019fcb --- /dev/null +++ b/variants/thinknode_m3/ThinknodeM3Board.cpp @@ -0,0 +1,80 @@ +#include +#include "ThinknodeM3Board.h" +#include + +#include + +void ThinknodeM3Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + btn_prev_state = HIGH; + + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + + // Enable DC/DC converter for improved power efficiency + NRF_POWER->DCDCEN = 1; + + Wire.begin(); + + delay(10); // give sx1262 some time to power up +} + +#if 0 +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + + +bool TrackerThinknodeM3Board::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("T1000E_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} +#endif \ No newline at end of file diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinknodeM3Board.h new file mode 100644 index 00000000..c9b96273 --- /dev/null +++ b/variants/thinknode_m3/ThinknodeM3Board.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +#define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX) + +class ThinknodeM3Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + uint8_t btn_prev_state; + +public: + void begin(); + + uint16_t getBattMilliVolts() override { + int adcvalue = 0; + + analogReference(AR_INTERNAL_2_4); + analogReadResolution(ADC_RESOLUTION); + delay(10); + + // ADC range is 0..2400mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * ADC_FACTOR); + } + + uint8_t getStartupReason() const override { return startup_reason; } + + #if defined(P_LORA_TX_LED) + #if !defined(P_LORA_TX_LED_ON) + #define P_LORA_TX_LED_ON HIGH + #endif + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, P_LORA_TX_LED_ON); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, !P_LORA_TX_LED_ON); // turn TX LED off + } + #endif + + const char* getManufacturerName() const override { + return "Elecrow ThinkNode M3"; + } + + int buttonStateChanged() { + #ifdef BUTTON_PIN + uint8_t v = digitalRead(BUTTON_PIN); + if (v != btn_prev_state) { + btn_prev_state = v; + return (v == LOW) ? 1 : -1; + } + #endif + return 0; + } + + void powerOff() override { + sd_power_system_off(); + } + + void reboot() override { + NVIC_SystemReset(); + } + +// bool startOTAUpdate(const char* id, char reply[]) override; +}; \ No newline at end of file diff --git a/variants/thinknode_m3/platformio.ini b/variants/thinknode_m3/platformio.ini new file mode 100644 index 00000000..8ef2ba54 --- /dev/null +++ b/variants/thinknode_m3/platformio.ini @@ -0,0 +1,122 @@ +[ThinkNode_M3] +extends = nrf52_base +board = thinknode_m3 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/thinknode_m3 + -I src/helpers/ui + -D THINKNODE_M3 + -D PIN_USER_BTN=12 + -D USER_BTN_PRESSED=LOW + -D PIN_STATUS_LED=35 + -D RADIO_CLASS=CustomLR1110 + -D WRAPPER_CLASS=CustomLR1110Wrapper + -D LORA_TX_POWER=22 + -D RF_SWITCH_TABLE + -D RX_BOOSTED_GAIN=true + -D P_LORA_BUSY=43 + -D P_LORA_SCLK=45 + -D P_LORA_NSS=44 + -D P_LORA_DIO_1=40 + -D P_LORA_MISO=47 + -D P_LORA_MOSI=46 + -D P_LORA_RESET=42 + -D P_LORA_TX_LED=PIN_LED_BLUE + -D P_LORA_TX_LED_ON=LOW + -D LR11X0_DIO_AS_RF_SWITCH=true + -D LR11X0_DIO3_TCXO_VOLTAGE=3.3 + -D MESH_DEBUG=1 + -D ENV_INCLUDE_GPS=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/thinknode_m3> + + +debug_tool = stlink +upload_protocol = nrfutil +lib_deps= ${nrf52_base.lib_deps} + +[env:ThinkNode_M3_repeater] +extends = ThinkNode_M3 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D ADVERT_NAME='"ThinkNode_M3 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<../examples/simple_repeater> +lib_deps = ${ThinkNode_M3.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:ThinkNode_M3_room_server] +extends = ThinkNode_M3 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D ADVERT_NAME='"ThinkNode_M3 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D RF_SWITCH_TABLE +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<../examples/simple_room_server> +lib_deps = ${ThinkNode_M3.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:ThinkNode_M3_companion_radio_usb] +extends = ThinkNode_M3 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_BUZZER=23 + -D PIN_BUZZER_EN=36 +build_src_filter = ${ThinkNode_M3.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${ThinkNode_M3.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M3_companion_radio_ble] +extends = ThinkNode_M3 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_TX_POWER=0 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 + -D GPS_NMEA_DEBUG + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_BUZZER=23 + -D PIN_BUZZER_EN=36 +build_src_filter = ${ThinkNode_M3.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${ThinkNode_M3.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp new file mode 100644 index 00000000..c6708e4d --- /dev/null +++ b/variants/thinknode_m3/target.cpp @@ -0,0 +1,99 @@ +#include +#include "target.h" +#include + +ThinknodeM3Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + NullDisplayDriver display; +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +#ifdef RF_SWITCH_TABLE +static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC, + RADIOLIB_NC, + RADIOLIB_NC +}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + { LR11x0::MODE_STBY, {LOW , LOW }}, + { LR11x0::MODE_RX, {HIGH, LOW }}, + { LR11x0::MODE_TX, {HIGH, HIGH }}, + { LR11x0::MODE_TX_HP, {LOW , HIGH }}, + { LR11x0::MODE_TX_HF, {LOW , LOW }}, + { LR11x0::MODE_GNSS, {LOW , LOW }}, + { LR11x0::MODE_WIFI, {LOW , LOW }}, + END_OF_MODE_TABLE, +}; +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + +#ifdef LR11X0_DIO3_TCXO_VOLTAGE + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(2); + radio.explicitHeader(); + +#ifdef RF_SWITCH_TABLE + radio.setRfSwitchTable(rfswitch_dios, rfswitch_table); +#endif +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + + return true; // success +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} \ No newline at end of file diff --git a/variants/thinknode_m3/target.h b/variants/thinknode_m3/target.h new file mode 100644 index 00000000..f60a85b0 --- /dev/null +++ b/variants/thinknode_m3/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include "ThinknodeM3Board.h" +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include "NullDisplayDriver.h" +#endif + +#ifdef DISPLAY_CLASS + extern NullDisplayDriver display; +#endif + +extern ThinknodeM3Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m3/variant.cpp b/variants/thinknode_m3/variant.cpp new file mode 100644 index 00000000..dad0f3f5 --- /dev/null +++ b/variants/thinknode_m3/variant.cpp @@ -0,0 +1,95 @@ +/* + * variant.cpp + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = +{ + 0, // P0.00 + 1, // P0.01 + 2, // P0.02 + 3, // P0.03 + 4, // P0.04 + 5, // P0.05 + 6, // P0.06 + 7, // P0.07 + 8, // P0.08 + 9, // P0.09 + 10, // P0.10 + 11, // P0.11 + 12, // P0.12 + 13, // P0.13 + 14, // P0.14 + 15, // P0.15 + 16, // P0.16 + 17, // P0.17 + 18, // P0.18 + 19, // P0.19 + 20, // P0.20 + 21, // P0.21 + 22, // P0.22 + 23, // P0.23 + 24, // P0.24 + 25, // P0.25 + 26, // P0.26 + 27, // P0.27 + 28, // P0.28 + 29, // P0.29 + 30, // P0.30 + 31, // P0.31 + 32, // P1.00 + 33, // P1.01 + 34, // P1.02 + 35, // P1.03 + 36, // P1.04 + 37, // P1.05 + 38, // P1.06 + 39, // P1.07 + 40, // P1.08 + 41, // P1.09 + 42, // P1.10 + 43, // P1.11 + 44, // P1.12 + 45, // P1.13 + 46, // P1.14 + 47, // P1.15 +}; + +void initVariant() +{ +/* TODO */ + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(BAT_POWER, OUTPUT); + digitalWrite(BAT_POWER, HIGH); + pinMode(EEPROM_POWER, OUTPUT); + digitalWrite(EEPROM_POWER, HIGH); + + pinMode(36, OUTPUT); + digitalWrite(36, HIGH); + pinMode(34, OUTPUT); + digitalWrite(34, HIGH); + + pinMode(LED_POWER, OUTPUT); + digitalWrite(LED_POWER, HIGH); + + pinMode(PIN_LED_BLUE, OUTPUT); + pinMode(PIN_LED_GREEN, OUTPUT); + pinMode(PIN_LED_RED, OUTPUT); + + pinMode(BUTTON_PIN, INPUT_PULLUP); + + pinMode(PIN_GPS_POWER, OUTPUT); + pinMode(PIN_GPS_EN, OUTPUT); + pinMode(PIN_GPS_RESET, OUTPUT); + + // Power on gps but in standby + digitalWrite(PIN_GPS_EN, LOW); + digitalWrite(PIN_GPS_POWER, HIGH); +} diff --git a/variants/thinknode_m3/variant.h b/variants/thinknode_m3/variant.h new file mode 100644 index 00000000..02ed78a8 --- /dev/null +++ b/variants/thinknode_m3/variant.h @@ -0,0 +1,109 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) +// #define USE_LFRC // 32.768 kHz RC oscillator + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define NRF_APM // detect usb power + + +#define EXT_CHRG_DETECT (32) // P1.3 +#define EXT_PWR_DETECT (31) // P0.5 + +#define PIN_VBAT_READ (5) +#define AREF_VOLTAGE (2.4f) +#define ADC_MULTIPLIER (2.0) //(1.75f) +// 2.0 gives more coherent value, 4.2V when charged, needs tweaking +#define ADC_RESOLUTION (12) +#define ADC_MAX (4096) + +#define EEPROM_POWER (7) +#define BAT_POWER (17) +#define PIN_PWR_EN (16) + + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define HAS_WIRE (1) +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (26) // P0.26 +#define PIN_WIRE_SCL (27) // P0.27 +#define I2C_NO_RESCAN + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (47) // P1.15 +#define PIN_SPI_MOSI (46) // P1.14 +#define PIN_SPI_SCK (45) // P1.13 +#define PIN_SPI_NSS (44) // P1.12 + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_POWER (29) +#define LED_BLUE (-1) // No blue led +#define PIN_LED_BLUE (37) +#define PIN_LED_GREEN (35) // P0.24 +#define PIN_LED_RED (33) +#define LED_PIN PIN_LED_GREEN +#define LED_BUILTIN PIN_LED_BLUE +#define LED_STATE_ON LOW + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (12) // P0.12 +#define BUTTON_PIN PIN_BUTTON1 + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define HAS_GPS 1 +#define PIN_GPS_RX (22) +#define PIN_GPS_TX (20) + +#define PIN_GPS_POWER (14) +#define PIN_GPS_EN (21) // STANDBY +#define PIN_GPS_RESET (25) // REINIT +#define GPS_RESET_ACTIVE LOW +#define GPS_EN_ACTIVE HIGH +#define GPS_BAUDRATE 9600 + +//////////////////////////////////////////////////////////////////////////////// +// Buzzer + +#define BUZZER_EN (37) // P1.5 +#define BUZZER_PIN (25) // P0.25 \ No newline at end of file From 0df8c86b98119eb667e22290897ceb14a3023786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Fri, 12 Dec 2025 17:24:28 +0000 Subject: [PATCH 42/42] Refactor devcontainer runArgs --- .devcontainer/devcontainer.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b734fe6b..fcde5048 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,10 +9,12 @@ } }, "runArgs": [ - "--network=host", "--privileged", - "--volume", - "/dev/bus/usb:/dev/bus/usb" + // arch tty* is owned by uucp (986) + // debian tty* is owned by uucp (20) - no change needed + "--group-add=986", + "--network=host", + "--volume=/dev/bus/usb:/dev/bus/usb:ro" ], "postCreateCommand": { "platformio": "pipx install platformio"