From ffc9815e9a26a01245f6032fa341d0ba88d8b41d Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Tue, 17 Feb 2026 23:54:33 +0100 Subject: [PATCH 1/5] Fix packet pool leak when rx queue is full MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PacketQueue::add() silently dropped packets when the queue was at capacity. The packet pointer was lost — never enqueued, never returned to the unused pool. Each occurrence permanently shrank the 32-packet pool until allocNew() returned NULL and the node went deaf. Return bool from add() and free the packet back to the pool on failure. --- src/helpers/StaticPoolPacketManager.cpp | 16 +++++++++++----- src/helpers/StaticPoolPacketManager.h | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 4f28eac6..125efb75 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -55,15 +55,15 @@ mesh::Packet* PacketQueue::removeByIdx(int i) { return item; } -void PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { +bool PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { if (_num == _size) { - // TODO: log "FATAL: queue is full!" - return; + return false; } _table[_num] = packet; _pri_table[_num] = priority; _schedule_table[_num] = scheduled_for; _num++; + return true; } StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): unused(pool_size), send_queue(pool_size), rx_queue(pool_size) { @@ -82,7 +82,10 @@ void StaticPoolPacketManager::free(mesh::Packet* packet) { } void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { - send_queue.add(packet, priority, scheduled_for); + if (!send_queue.add(packet, priority, scheduled_for)) { + MESH_DEBUG_PRINTLN("queueOutbound: send queue full, dropping packet"); + free(packet); + } } mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) { @@ -106,7 +109,10 @@ mesh::Packet* StaticPoolPacketManager::removeOutboundByIdx(int i) { } void StaticPoolPacketManager::queueInbound(mesh::Packet* packet, uint32_t scheduled_for) { - rx_queue.add(packet, 0, scheduled_for); + if (!rx_queue.add(packet, 0, scheduled_for)) { + MESH_DEBUG_PRINTLN("queueInbound: rx queue full, dropping packet"); + free(packet); + } } mesh::Packet* StaticPoolPacketManager::getNextInbound(uint32_t now) { return rx_queue.get(now); diff --git a/src/helpers/StaticPoolPacketManager.h b/src/helpers/StaticPoolPacketManager.h index bbf4b193..52c299db 100644 --- a/src/helpers/StaticPoolPacketManager.h +++ b/src/helpers/StaticPoolPacketManager.h @@ -11,7 +11,7 @@ class PacketQueue { public: PacketQueue(int max_entries); mesh::Packet* get(uint32_t now); - void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for); + bool add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for); int count() const { return _num; } int countBefore(uint32_t now) const; mesh::Packet* itemAt(int i) const { return _table[i]; } From 1500a5a9cbfe2df8bbd06ba08e5e58bd5f319f5e Mon Sep 17 00:00:00 2001 From: taco Date: Wed, 18 Feb 2026 15:35:20 +1100 Subject: [PATCH 2/5] add get bootloader.ver command for nrf52 --- src/MeshCore.h | 1 + src/helpers/CommonCLI.cpp | 11 +++++++++++ src/helpers/NRF52Board.cpp | 19 +++++++++++++++++++ src/helpers/NRF52Board.h | 1 + 4 files changed, 32 insertions(+) diff --git a/src/MeshCore.h b/src/MeshCore.h index f194cdeb..70cd0f06 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -55,6 +55,7 @@ public: virtual uint32_t getGpio() { return 0; } virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; + virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; } virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported // Power management interface (boards with power management override these) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 6dcf7018..263eb665 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -362,6 +362,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "bridge.secret", 13) == 0) { sprintf(reply, "> %s", _prefs->bridge_secret); #endif + } else if (memcmp(config, "bootloader.ver", 14) == 0) { + #ifdef NRF52_PLATFORM + char ver[32]; + if (_board->getBootloaderVersion(ver, sizeof(ver))) { + sprintf(reply, "> %s", ver); + } else { + strcpy(reply, "> unknown"); + } + #else + strcpy(reply, "ERROR: unsupported"); + #endif } else if (memcmp(config, "adc.multiplier", 14) == 0) { float adc_mult = _board->getAdcMultiplier(); if (adc_mult == 0.0f) { diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 1db858f5..2c8753d4 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -297,6 +297,25 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } +bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) { + static const char BOOTLOADER_MARKER[] = "UF2 Bootloader "; + const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B + + for (uint32_t i = 0; i < 0x3000 - (sizeof(BOOTLOADER_MARKER) - 1); i++) { + if (memcmp(&flash[i], BOOTLOADER_MARKER, sizeof(BOOTLOADER_MARKER) - 1) == 0) { + const char* ver = (const char*)&flash[i + sizeof(BOOTLOADER_MARKER) - 1]; + size_t len = 0; + while (len < max_len - 1 && ver[len] != '\0' && ver[len] != ' ' && ver[len] != '\n' && ver[len] != '\r') { + out[len] = ver[len]; + len++; + } + out[len] = '\0'; + return len > 0; // bootloader string is non-empty + } + } + return false; +} + bool NRF52Board::startOTAUpdate(const char *id, char reply[]) { // Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 0332af07..96f67dc9 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -50,6 +50,7 @@ public: virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + virtual bool getBootloaderVersion(char* version, size_t max_len) override; virtual bool startOTAUpdate(const char *id, char reply[]) override; virtual void sleep(uint32_t secs) override; From 3dc14976a03d46f855ce21faddc64866471aa702 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Sun, 22 Feb 2026 14:46:45 +0100 Subject: [PATCH 3/5] add companion usb build target for Heltec Wireless Tracker --- variants/heltec_tracker/platformio.ini | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 797eafdc..dba05dcf 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -43,6 +43,31 @@ lib_deps = stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 +[env:Heltec_Wireless_Tracker_companion_radio_usb] +extends = Heltec_tracker_base +build_flags = + ${Heltec_tracker_base.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D DISPLAY_ROTATION=1 + -D DISPLAY_CLASS=ST7735Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D BLE_PIN_CODE=123456 ; HWT will use display for pin +; -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_base.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + + +lib_deps = + ${Heltec_tracker_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Heltec_Wireless_Tracker_companion_radio_ble] extends = Heltec_tracker_base build_flags = From 011edd3c999ca6c528016d67f3e68ac46830131c Mon Sep 17 00:00:00 2001 From: Daniel Novak Date: Sun, 22 Feb 2026 18:01:30 +0100 Subject: [PATCH 4/5] Fix millis() wraparound in PacketQueue time comparisons PacketQueue::countBefore() and PacketQueue::get() use unsigned comparison (_schedule_table[j] > now) to check if a packet is scheduled for the future. This breaks when millis() wraps around after ~49.7 days: packets scheduled just before the wrap appear to be in the far future and get stuck in the queue. Use signed subtraction instead, matching the approach already used by Dispatcher::millisHasNowPassed(). This correctly handles the wraparound for time differences up to ~24.8 days in either direction, well beyond the maximum queue delay of 32 seconds. --- src/helpers/StaticPoolPacketManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 125efb75..67d63979 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -11,7 +11,7 @@ PacketQueue::PacketQueue(int max_entries) { int PacketQueue::countBefore(uint32_t now) const { int n = 0; for (int j = 0; j < _num; j++) { - if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now + if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now n++; } return n; @@ -21,7 +21,7 @@ mesh::Packet* PacketQueue::get(uint32_t now) { uint8_t min_pri = 0xFF; int best_idx = -1; for (int j = 0; j < _num; j++) { - if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now + if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now if (_pri_table[j] < min_pri) { // select most important priority amongst non-future entries min_pri = _pri_table[j]; best_idx = j; From 5a885bffe4ad6e9e33771845a7e43e3a0db6d6d9 Mon Sep 17 00:00:00 2001 From: Sam Koucha <23366921+ElectroMW@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:14:39 +0000 Subject: [PATCH 5/5] Make full use of board's 8MB Flash and add companion WiFI target --- boards/t_beams3_supreme.json | 2 +- .../platformio.ini | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/boards/t_beams3_supreme.json b/boards/t_beams3_supreme.json index 6a725247..3eb9c016 100644 --- a/boards/t_beams3_supreme.json +++ b/boards/t_beams3_supreme.json @@ -41,7 +41,7 @@ "name": "LilyGo T-Beam supreme (8MB Flash 8MB PSRAM)", "upload": { "flash_size": "8MB", - "maximum_ram_size": 327680, + "maximum_ram_size": 8388608, "maximum_size": 8388608, "require_upload_port": true, "speed": 460800 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 2d2a095a..04e4b0bf 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -26,7 +26,9 @@ build_src_filter = ${esp32_base.build_src_filter} + + + -board_build.partitions = min_spiffs.csv ; get around 4mb flash limit +board_build.partitions = default_8MB.csv +board_upload.flash_size = 8MB +board_upload.maximum_size = 8388608 lib_deps = ${esp32_base.lib_deps} lewisxhe/XPowersLib @ ^0.2.7 @@ -131,3 +133,27 @@ build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:T_Beam_S3_Supreme_SX1262_companion_radio_wifi] +extends = T_Beam_S3_Supreme_SX1262 +build_flags = + ${T_Beam_S3_Supreme_SX1262.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_SSID='"Three_CA7C65"' + -D WIFI_PWD='"8hC45a66265eA3w"' +; -D WIFI_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=8 +; -D MESH_DEBUG=1 +; -D ARDUHAL_LOG_LEVEL=4 +; -D CORE_DEBUG_LEVEL=4 +build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${T_Beam_S3_Supreme_SX1262.lib_deps} + densaugeo/base64 @ ~1.4.0