From d559af8477d207da88d8f687fea7d134eaf13deb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 12:29:04 +0200 Subject: [PATCH 1/6] Update LovyanGFX to v1.2.21 (#10373) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/chatter2/platformio.ini | 2 +- variants/esp32/m5stack_core/platformio.ini | 2 +- variants/esp32/wiphone/platformio.ini | 2 +- variants/esp32s3/heltec_v4/platformio.ini | 2 +- variants/esp32s3/heltec_v4_r8/platformio.ini | 2 +- variants/esp32s3/heltec_wireless_tracker/platformio.ini | 2 +- variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini | 2 +- variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini | 2 +- variants/esp32s3/mesh-tab/platformio.ini | 2 +- variants/esp32s3/picomputer-s3/platformio.ini | 2 +- variants/esp32s3/rak_wismesh_tap_v2/platformio.ini | 2 +- variants/esp32s3/t-deck/platformio.ini | 2 +- variants/esp32s3/t-watch-s3/platformio.ini | 2 +- variants/esp32s3/tlora-pager/platformio.ini | 2 +- variants/esp32s3/tracksenger/platformio.ini | 4 ++-- variants/esp32s3/unphone/platformio.ini | 2 +- variants/native/portduino.ini | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index a14e407a1..b0adeee4b 100644 --- a/variants/esp32/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -12,4 +12,4 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index 8fbbae895..4f0b556ac 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -35,4 +35,4 @@ lib_ignore = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 diff --git a/variants/esp32/wiphone/platformio.ini b/variants/esp32/wiphone/platformio.ini index fbd77be75..b1c1f8bf7 100644 --- a/variants/esp32/wiphone/platformio.ini +++ b/variants/esp32/wiphone/platformio.ini @@ -11,7 +11,7 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 # renovate: datasource=custom.pio depName=SX1509 IO Expander packageName=sparkfun/library/SX1509 IO Expander sparkfun/SX1509 IO Expander@3.0.6 # renovate: datasource=custom.pio depName=APA102 packageName=pololu/library/APA102 diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 103cac941..790f02924 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -133,6 +133,6 @@ build_flags = lib_deps = ${heltec_v4_base.lib_deps} ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 # renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip \ No newline at end of file diff --git a/variants/esp32s3/heltec_v4_r8/platformio.ini b/variants/esp32s3/heltec_v4_r8/platformio.ini index 747cc8c49..7799acf43 100644 --- a/variants/esp32s3/heltec_v4_r8/platformio.ini +++ b/variants/esp32s3/heltec_v4_r8/platformio.ini @@ -140,6 +140,6 @@ build_flags = lib_deps = ${heltec_v4_r8_base.lib_deps} ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 # renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master https://github.com/Quency-D/chsc6x/archive/3b2b6cebf3177b3e2c33d06e07909b0b10159516.zip \ No newline at end of file diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini index 33643c541..1450bb45c 100644 --- a/variants/esp32s3/heltec_wireless_tracker/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker/platformio.ini @@ -24,4 +24,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini index ab6592afb..ffcc1fe95 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini @@ -22,4 +22,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini index ebf0118bb..53f698c9e 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -21,4 +21,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 diff --git a/variants/esp32s3/mesh-tab/platformio.ini b/variants/esp32s3/mesh-tab/platformio.ini index a153ba9fb..fcb58d36a 100644 --- a/variants/esp32s3/mesh-tab/platformio.ini +++ b/variants/esp32s3/mesh-tab/platformio.ini @@ -55,7 +55,7 @@ lib_deps = ${esp32s3_base.lib_deps} ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 [mesh_tab_xpt2046] extends = mesh_tab_base diff --git a/variants/esp32s3/picomputer-s3/platformio.ini b/variants/esp32s3/picomputer-s3/platformio.ini index 6f218a126..b5a4ff178 100644 --- a/variants/esp32s3/picomputer-s3/platformio.ini +++ b/variants/esp32s3/picomputer-s3/platformio.ini @@ -25,7 +25,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 build_src_filter = ${esp32s3_base.build_src_filter} diff --git a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini index 7847410ae..1d3314aab 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini +++ b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini @@ -37,7 +37,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 [env:rak_wismesh_tap_v2-tft] extends = env:rak_wismesh_tap_v2 diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini index 1b3599464..a7701549f 100644 --- a/variants/esp32s3/t-deck/platformio.ini +++ b/variants/esp32s3/t-deck/platformio.ini @@ -29,7 +29,7 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 # renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM diff --git a/variants/esp32s3/t-watch-s3/platformio.ini b/variants/esp32s3/t-watch-s3/platformio.ini index 352396818..6e7db8630 100644 --- a/variants/esp32s3/t-watch-s3/platformio.ini +++ b/variants/esp32s3/t-watch-s3/platformio.ini @@ -22,7 +22,7 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 # renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 832f9d7d7..15abfadf3 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -33,7 +33,7 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 # renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip # renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM diff --git a/variants/esp32s3/tracksenger/platformio.ini b/variants/esp32s3/tracksenger/platformio.ini index c006cf835..44d07d9e8 100644 --- a/variants/esp32s3/tracksenger/platformio.ini +++ b/variants/esp32s3/tracksenger/platformio.ini @@ -22,7 +22,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 [env:tracksenger-lcd] custom_meshtastic_hw_model = 48 @@ -48,7 +48,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 [env:tracksenger-oled] custom_meshtastic_hw_model = 48 diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index 3c342e2ac..56838b1fc 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -37,7 +37,7 @@ build_src_filter = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 https://gitlab.com/hamishcunningham/unphonelibrary/-/archive/meshtastic/unphonelibrary-meshtastic.zip diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index 6ff3d0686..9ab45d1ab 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -27,7 +27,7 @@ lib_deps = # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto rweather/Crypto@0.4.0 # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.19 + lovyan03/LovyanGFX@1.2.21 ; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library From 603cce2988b8640d94199403ad6bd35b23062fd0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 5 May 2026 10:12:50 -0500 Subject: [PATCH 2/6] Add informSearchFailed method to update GPS power state handling (#10394) --- src/gps/GPS.cpp | 7 +++++-- src/gps/GPSUpdateScheduling.cpp | 10 ++++++++++ src/gps/GPSUpdateScheduling.h | 3 ++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 1260d8b15..f42678182 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1017,10 +1017,13 @@ void GPS::up() setPowerState(GPS_ACTIVE); } -// We've got a GPS lock. Enter a low power state, potentially. +// We've finished a GPS search cycle (lock or timeout). Enter a low power state, potentially. void GPS::down() { - scheduling.informGotLock(); + if (hasValidLocation) + scheduling.informGotLock(); + else + scheduling.informSearchFailed(); uint32_t predictedSearchDuration = scheduling.predictedSearchDurationMs(); uint32_t sleepTime = scheduling.msUntilNextSearch(); uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval); diff --git a/src/gps/GPSUpdateScheduling.cpp b/src/gps/GPSUpdateScheduling.cpp index 53d6c833f..45634d2d3 100644 --- a/src/gps/GPSUpdateScheduling.cpp +++ b/src/gps/GPSUpdateScheduling.cpp @@ -17,6 +17,16 @@ void GPSUpdateScheduling::informGotLock() updateLockTimePrediction(); } +// Search finished without obtaining a fix. We still need to mark the end time so +// the next sleep is timed correctly, but we must not feed the timeout duration +// into predictedMsToGetLock — doing so poisons msUntilNextSearch() and causes +// down() to fall into GPS_IDLE, leaving the chip awake on subsequent indoor cycles. +void GPSUpdateScheduling::informSearchFailed() +{ + searchEndedMs = millis(); + LOG_DEBUG("GPS search ended without fix after %us", (searchEndedMs - searchStartedMs) / 1000); +} + // Clear old lock-time prediction data. // When re-enabling GPS with user button. void GPSUpdateScheduling::reset() diff --git a/src/gps/GPSUpdateScheduling.h b/src/gps/GPSUpdateScheduling.h index 7e121c9b6..64835a469 100644 --- a/src/gps/GPSUpdateScheduling.h +++ b/src/gps/GPSUpdateScheduling.h @@ -8,7 +8,8 @@ class GPSUpdateScheduling public: // Marks the time of these events, for calculation use void informSearching(); - void informGotLock(); // Predicted lock-time is recalculated here + void informGotLock(); // Predicted lock-time is recalculated here + void informSearchFailed(); // Search ended without a fix; prediction is left untouched void reset(); // Reset the prediction - after GPS::disable() / GPS::enable() bool isUpdateDue(); // Is it time to begin searching for a GPS position? From 6e810741f329a77011a4941e2a839fad43a2e800 Mon Sep 17 00:00:00 2001 From: jessm33 <112707725+jessm33@users.noreply.github.com> Date: Tue, 5 May 2026 15:17:04 -0400 Subject: [PATCH 3/6] Fix GPS initialization logic for Portduino configuration (#10395) Co-authored-by: jessm33 --- src/gps/GPS.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index f42678182..3bc047ad8 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1548,7 +1548,12 @@ std::unique_ptr GPS::createGps() _en_gpio = PIN_GPS_EN; #endif #ifdef ARCH_PORTDUINO - if (!portduino_config.has_gps) + if (portduino_config.has_gps) { + // These need to set as flags so later checks will pass on native and GPS will work. + // They are not used for any hardware access. + _rx_gpio = 1; + _tx_gpio = 1; + } else return nullptr; #endif if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all From 220bb4d1865ffc7536d387f794e420060317adf2 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 6 May 2026 15:33:59 -0500 Subject: [PATCH 4/6] Smart pointers and memory management cleanup (#10400) * Refactor memory management in Syslog and StoreForwardModule * Implement destructor for Lock * Refactor RotaryEncoder and PacketHistory to use smart pointers for better memory management * CH341 should use unique_ptr for improved memory management * Fix checks in PH * Improve Syslog::vlogf to handle variable argument lists more safely * Fix initOk method to use nullptr for null pointer check --- src/DebugConfiguration.cpp | 33 +++++++------- src/concurrency/Lock.cpp | 7 +++ src/concurrency/Lock.h | 1 + src/input/RotaryEncoderImpl.cpp | 11 ++--- src/input/RotaryEncoderImpl.h | 3 +- src/mesh/PacketHistory.cpp | 55 ++++++++++-------------- src/mesh/PacketHistory.h | 8 ++-- src/modules/StoreForwardModule.cpp | 4 +- src/modules/StoreForwardModule.h | 9 +++- src/platform/portduino/PortduinoGlue.cpp | 8 ++-- 10 files changed, 68 insertions(+), 71 deletions(-) diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index 207feb8c0..f83624bbf 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -26,6 +26,8 @@ SOFTWARE.*/ #include "DebugConfiguration.h" +#include + #ifdef ARCH_PORTDUINO #include "platform/portduino/PortduinoGlue.h" #endif @@ -119,27 +121,22 @@ bool Syslog::vlogf(uint16_t pri, const char *fmt, va_list args) bool Syslog::vlogf(uint16_t pri, const char *appName, const char *fmt, va_list args) { - char *message; - size_t initialLen; - size_t len; - bool result; + // First measure the formatted length using a copy of args; passing args directly + // to vsnprintf consumes it, and reusing a consumed va_list is undefined behavior. + va_list args_measure; + va_copy(args_measure, args); + int needed = vsnprintf(nullptr, 0, fmt, args_measure); + va_end(args_measure); - initialLen = strlen(fmt); + if (needed < 0) + return false; // encoding error - message = new char[initialLen + 1]; + auto message = std::unique_ptr(new char[static_cast(needed) + 1]); + int written = vsnprintf(message.get(), static_cast(needed) + 1, fmt, args); + if (written < 0) + return false; - len = vsnprintf(message, initialLen + 1, fmt, args); - if (len > initialLen) { - delete[] message; - message = new char[len + 1]; - - vsnprintf(message, len + 1, fmt, args); - } - - result = this->_sendLog(pri, appName, message); - - delete[] message; - return result; + return this->_sendLog(pri, appName, message.get()); } inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *message) diff --git a/src/concurrency/Lock.cpp b/src/concurrency/Lock.cpp index 0fe80e455..4596e0edf 100644 --- a/src/concurrency/Lock.cpp +++ b/src/concurrency/Lock.cpp @@ -14,6 +14,11 @@ Lock::Lock() : handle(xSemaphoreCreateBinary()) } } +Lock::~Lock() +{ + vSemaphoreDelete(handle); +} + void Lock::lock() { if (xSemaphoreTake(handle, portMAX_DELAY) == false) { @@ -30,6 +35,8 @@ void Lock::unlock() #else Lock::Lock() {} +Lock::~Lock() {} + void Lock::lock() {} void Lock::unlock() {} diff --git a/src/concurrency/Lock.h b/src/concurrency/Lock.h index 1b9ea20d5..a51248117 100644 --- a/src/concurrency/Lock.h +++ b/src/concurrency/Lock.h @@ -12,6 +12,7 @@ class Lock { public: Lock(); + ~Lock(); Lock(const Lock &) = delete; Lock &operator=(const Lock &) = delete; diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp index cc1222595..dcdbf0d36 100644 --- a/src/input/RotaryEncoderImpl.cpp +++ b/src/input/RotaryEncoderImpl.cpp @@ -13,7 +13,6 @@ RotaryEncoderImpl *rotaryEncoderImpl; RotaryEncoderImpl::RotaryEncoderImpl() { - rotary = nullptr; #ifdef ARCH_ESP32 isFirstInit = true; #endif @@ -23,11 +22,6 @@ RotaryEncoderImpl::~RotaryEncoderImpl() { LOG_DEBUG("RotaryEncoderImpl destructor"); detachRotaryEncoderInterrupts(); - - if (rotary != nullptr) { - delete rotary; - rotary = nullptr; - } } bool RotaryEncoderImpl::init() @@ -43,8 +37,9 @@ bool RotaryEncoderImpl::init() eventPressed = static_cast(moduleConfig.canned_message.inputbroker_event_press); if (rotary == nullptr) { - rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b, - moduleConfig.canned_message.inputbroker_pin_press); + rotary.reset(new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, + moduleConfig.canned_message.inputbroker_pin_b, + moduleConfig.canned_message.inputbroker_pin_press)); } attachRotaryEncoderInterrupts(); diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h index ec8a064bd..58b4e3450 100644 --- a/src/input/RotaryEncoderImpl.h +++ b/src/input/RotaryEncoderImpl.h @@ -5,6 +5,7 @@ #include "InputBroker.h" #include "concurrency/OSThread.h" #include "mesh/NodeDB.h" +#include class RotaryEncoder; @@ -28,7 +29,7 @@ class RotaryEncoderImpl final : public InputPollable input_broker_event eventCcw = INPUT_BROKER_NONE; input_broker_event eventPressed = INPUT_BROKER_NONE; - RotaryEncoder *rotary; + std::unique_ptr rotary; private: #ifdef ARCH_ESP32 diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 845a936d4..861b5fdfb 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -16,7 +16,7 @@ #define VERBOSE_PACKET_HISTORY 0 // Set to 1 for verbose logging, 2 for heavy debugging #define PACKET_HISTORY_TRACE_AGING 1 // Set to 1 to enable logging of the age of re/used history slots -PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPackets(NULL) // Initialize members +PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0) // Initialize members { if (size < 4 || size > PACKETHISTORY_MAX) { // Copilot suggested - makes sense LOG_WARN("Packet History - Invalid size %d, using default %d", size, PACKETHISTORY_MAX); @@ -25,7 +25,7 @@ PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPa // Allocate memory for the recent packets array recentPacketsCapacity = size; - recentPackets = new PacketRecord[recentPacketsCapacity]; + recentPackets.reset(new PacketRecord[recentPacketsCapacity]); if (!recentPackets) { // No logging here, console/log probably uninitialized yet. LOG_ERROR("Packet History - Memory allocation failed for size=%d entries / %d Bytes", size, sizeof(PacketRecord) * recentPacketsCapacity); @@ -34,14 +34,7 @@ PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPa } // Initialize the recent packets array to zero - memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity); -} - -PacketHistory::~PacketHistory() -{ - recentPacketsCapacity = 0; - delete[] recentPackets; - recentPackets = NULL; + memset(recentPackets.get(), 0, sizeof(PacketRecord) * recentPacketsCapacity); } /** Update recentPackets and return true if we have already seen this packet */ @@ -205,13 +198,14 @@ PacketHistory::PacketRecord *PacketHistory::find(NodeNum sender, PacketId id) return NULL; } + PacketRecord *base = recentPackets.get(); PacketRecord *it = NULL; - for (it = recentPackets; it < (recentPackets + recentPacketsCapacity); ++it) { + for (it = base; it < (base + recentPacketsCapacity); ++it) { if (it->id == id && it->sender == sender) { #if VERBOSE_PACKET_HISTORY LOG_DEBUG("Packet History - find: s=%08x id=%08x FOUND nh=%02x rby=%02x %02x %02x age=%d slot=%d/%d", it->sender, it->id, it->next_hop, it->relayed_by[0], it->relayed_by[1], it->relayed_by[2], millis() - (it->rxTimeMsec), - it - recentPackets, recentPacketsCapacity); + it - base, recentPacketsCapacity); #endif // only the first match is returned, so be careful not to create duplicate entries return it; // Return pointer to the found record @@ -229,39 +223,38 @@ void PacketHistory::insert(const PacketRecord &r) { uint32_t now_millis = millis(); // Should not jump with time changes uint32_t OldtrxTimeMsec = 0; + PacketRecord *base = recentPackets.get(); PacketRecord *tu = NULL; // Will insert here. PacketRecord *it = NULL; // Find a free, matching or oldest used slot in the recentPackets array - for (it = recentPackets; it < (recentPackets + recentPacketsCapacity); ++it) { + for (it = base; it < (base + recentPacketsCapacity); ++it) { if (it->id == 0 && it->sender == 0 /*&& rxTimeMsec == 0*/) { // Record is empty tu = it; // Remember the free slot #if VERBOSE_PACKET_HISTORY >= 2 - LOG_DEBUG("Packet History - insert: Free slot@ %d/%d", tu - recentPackets, recentPacketsCapacity); + LOG_DEBUG("Packet History - insert: Free slot@ %d/%d", tu - base, recentPacketsCapacity); #endif // We have that, Exit the loop - it = (recentPackets + recentPacketsCapacity); + it = (base + recentPacketsCapacity); } else if (it->id == r.id && it->sender == r.sender) { // Record matches the packet we want to insert tu = it; // Remember the matching slot OldtrxTimeMsec = now_millis - it->rxTimeMsec; // ..and save current entry's age #if VERBOSE_PACKET_HISTORY >= 2 - LOG_DEBUG("Packet History - insert: Matched slot@ %d/%d age=%d", tu - recentPackets, recentPacketsCapacity, - OldtrxTimeMsec); + LOG_DEBUG("Packet History - insert: Matched slot@ %d/%d age=%d", tu - base, recentPacketsCapacity, OldtrxTimeMsec); #endif // We have that, Exit the loop - it = (recentPackets + recentPacketsCapacity); + it = (base + recentPacketsCapacity); } else { if (it->rxTimeMsec == 0) { LOG_WARN( "Packet History - insert: Found packet s=%08x id=%08x with rxTimeMsec = 0, slot %d/%d. Should never happen!", - it->sender, it->id, it - recentPackets, recentPacketsCapacity); + it->sender, it->id, it - base, recentPacketsCapacity); } if ((now_millis - it->rxTimeMsec) > OldtrxTimeMsec) { // 49.7 days rollover friendly OldtrxTimeMsec = now_millis - it->rxTimeMsec; tu = it; // remember the oldest packet #if VERBOSE_PACKET_HISTORY >= 2 - LOG_DEBUG("Packet History - insert: Older slot@ %d/%d age=%d", tu - recentPackets, recentPacketsCapacity, - OldtrxTimeMsec); + LOG_DEBUG("Packet History - insert: Older slot@ %d/%d age=%d", tu - base, recentPacketsCapacity, OldtrxTimeMsec); #endif } // keep looking for oldest till entire array is checked @@ -276,13 +269,11 @@ void PacketHistory::insert(const PacketRecord &r) #if VERBOSE_PACKET_HISTORY if (tu->id == 0 && tu->sender == 0) { - LOG_DEBUG("Packet History - insert: slot@ %d/%d is NEW", tu - recentPackets, recentPacketsCapacity); + LOG_DEBUG("Packet History - insert: slot@ %d/%d is NEW", tu - base, recentPacketsCapacity); } else if (tu->id == r.id && tu->sender == r.sender) { - LOG_DEBUG("Packet History - insert: slot@ %d/%d MATCHED, age=%d", tu - recentPackets, recentPacketsCapacity, - OldtrxTimeMsec); + LOG_DEBUG("Packet History - insert: slot@ %d/%d MATCHED, age=%d", tu - base, recentPacketsCapacity, OldtrxTimeMsec); } else { - LOG_DEBUG("Packet History - insert: slot@ %d/%d REUSE OLDEST, age=%d", tu - recentPackets, recentPacketsCapacity, - OldtrxTimeMsec); + LOG_DEBUG("Packet History - insert: slot@ %d/%d REUSE OLDEST, age=%d", tu - base, recentPacketsCapacity, OldtrxTimeMsec); } #endif @@ -315,9 +306,9 @@ void PacketHistory::insert(const PacketRecord &r) #endif #if VERBOSE_PACKET_HISTORY - LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d BEFORE", - tu - recentPackets, recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1], - tu->relayed_by[2], tu->rxTimeMsec); + LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d BEFORE", tu - base, + recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1], tu->relayed_by[2], + tu->rxTimeMsec); #endif if (r.rxTimeMsec == 0) { @@ -330,9 +321,9 @@ void PacketHistory::insert(const PacketRecord &r) *tu = r; // store the packet #if VERBOSE_PACKET_HISTORY - LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d AFTER", - tu - recentPackets, recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1], - tu->relayed_by[2], tu->rxTimeMsec); + LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d AFTER", tu - base, + recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1], tu->relayed_by[2], + tu->rxTimeMsec); #endif } diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 9b6a93280..756fa86c1 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -1,6 +1,7 @@ #pragma once #include "NodeDB.h" +#include // Number of relayers we keep track of. Use 6 to be efficient with memory alignment of PacketRecord to 20 bytes #define NUM_RELAYERS 6 @@ -26,7 +27,7 @@ class PacketHistory uint32_t recentPacketsCapacity = 0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets. - PacketRecord *recentPackets = NULL; // Simple and fixed in size. Debloat. + std::unique_ptr recentPackets; // Simple and fixed in size. Debloat. /** Find a packet record in history. * @param sender NodeNum @@ -48,11 +49,8 @@ class PacketHistory uint8_t getOurTxHopLimit(const PacketRecord &r); void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit); - PacketHistory(const PacketHistory &); // non construction-copyable - PacketHistory &operator=(const PacketHistory &); // non copyable public: explicit PacketHistory(uint32_t size = -1); // Constructor with size parameter, default is PACKETHISTORY_MAX - ~PacketHistory(); /** * Update recentBroadcasts and return true if we have already seen this packet @@ -74,5 +72,5 @@ class PacketHistory void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender); // To check if the PacketHistory was initialized correctly by constructor - bool initOk(void) { return recentPackets != NULL && recentPacketsCapacity != 0; } + bool initOk(void) { return recentPackets != nullptr && recentPacketsCapacity != 0; } }; diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 6df0e18f0..0ac70acf8 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -80,9 +80,9 @@ void StoreForwardModule::populatePSRAM() (this->records ? this->records : (((memGet.getFreePsram() / 4) * 3) / sizeof(PacketHistoryStruct))); this->records = numberOfPackets; #if defined(ARCH_ESP32) - this->packetHistory = static_cast(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct))); + this->packetHistory.reset(static_cast(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct)))); #elif defined(ARCH_PORTDUINO) - this->packetHistory = static_cast(calloc(numberOfPackets, sizeof(PacketHistoryStruct))); + this->packetHistory.reset(static_cast(calloc(numberOfPackets, sizeof(PacketHistoryStruct)))); #endif diff --git a/src/modules/StoreForwardModule.h b/src/modules/StoreForwardModule.h index 148568e1b..77565b22c 100644 --- a/src/modules/StoreForwardModule.h +++ b/src/modules/StoreForwardModule.h @@ -7,6 +7,7 @@ #include "configuration.h" #include #include +#include #include struct PacketHistoryStruct { @@ -29,11 +30,17 @@ struct PacketHistoryStruct { class StoreForwardModule : private concurrency::OSThread, public ProtobufModule { + // packetHistory is allocated with ps_calloc / calloc, so it must be released with free(), + // not delete[]. + struct CFreeDeleter { + void operator()(PacketHistoryStruct *p) const noexcept { free(p); } + }; + bool busy = 0; uint32_t busyTo = 0; char routerMessage[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0}; - PacketHistoryStruct *packetHistory = 0; + std::unique_ptr packetHistory; uint32_t packetHistoryTotalCount = 0; uint32_t last_time = 0; uint32_t requestCount = 0; diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 1f59e78b5..bfa2f6efb 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -297,10 +298,9 @@ void portduinoSetup() // Try CH341 try { std::cout << "autoconf: Looking for CH341 device..." << std::endl; - ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid, - portduino_config.lora_usb_pid); - ch341Hal->getProductString(autoconf_product, 95); - delete ch341Hal; + auto probe = std::unique_ptr(new Ch341Hal(0, portduino_config.lora_usb_serial_num, + portduino_config.lora_usb_vid, portduino_config.lora_usb_pid)); + probe->getProductString(autoconf_product, 95); std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl; found_ch341 = true; From 33e2bb70e6e3a0014c7a4aeef916c8f81de93506 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 6 May 2026 20:44:04 -0500 Subject: [PATCH 5/6] Enhance GPS search failure handling backoff logic (#10404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enhance GPS search failure handling backoff logic * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Remove stray submodule gitlink for .claude worktree A 160000 (gitlink) entry for .claude/worktrees/naughty-payne-60fdb7 pointing at f2923590bc was accidentally committed in 9db15780f. The path isn't a real submodule — it's a Claude Code agent worktree that shouldn't be tracked. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) --- src/gps/GPSUpdateScheduling.cpp | 28 +++++++++++++++++++++++++--- src/gps/GPSUpdateScheduling.h | 1 + 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/gps/GPSUpdateScheduling.cpp b/src/gps/GPSUpdateScheduling.cpp index 45634d2d3..07920e967 100644 --- a/src/gps/GPSUpdateScheduling.cpp +++ b/src/gps/GPSUpdateScheduling.cpp @@ -15,6 +15,7 @@ void GPSUpdateScheduling::informGotLock() searchEndedMs = millis(); LOG_DEBUG("Took %us to get lock", (searchEndedMs - searchStartedMs) / 1000); updateLockTimePrediction(); + consecutiveFailures = 0; // Drop back to fast cadence as soon as we acquire any fix } // Search finished without obtaining a fix. We still need to mark the end time so @@ -24,7 +25,9 @@ void GPSUpdateScheduling::informGotLock() void GPSUpdateScheduling::informSearchFailed() { searchEndedMs = millis(); - LOG_DEBUG("GPS search ended without fix after %us", (searchEndedMs - searchStartedMs) / 1000); + consecutiveFailures++; + LOG_DEBUG("GPS search ended without fix after %us (consecutive failures: %u)", (searchEndedMs - searchStartedMs) / 1000, + consecutiveFailures); } // Clear old lock-time prediction data. @@ -35,6 +38,7 @@ void GPSUpdateScheduling::reset() searchEndedMs = 0; searchCount = 0; predictedMsToGetLock = 0; + consecutiveFailures = 0; } // How many milliseconds before we should next search for GPS position @@ -46,6 +50,20 @@ uint32_t GPSUpdateScheduling::msUntilNextSearch() // Target interval (seconds), between GPS updates uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval, default_gps_update_interval); + // After a failed search, back off: indoors / no-sky environments will keep failing, + // so wake at most once per broadcast interval rather than once per gps_update_interval. + // Capped at 1 hour so a user-configured very-long broadcast interval still retries + // periodically (in case conditions change). Reset on any successful lock. + if (consecutiveFailures > 0) { + constexpr uint32_t failureRetryCapMs = 60UL * 60UL * 1000UL; // 1 hour cap + uint32_t failureSleepMs = + Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs); + if (failureSleepMs > failureRetryCapMs) + failureSleepMs = failureRetryCapMs; + if (updateInterval < failureSleepMs) + updateInterval = failureSleepMs; + } + // Check how long until we should start searching, to hopefully hit our target interval uint32_t dueAtMs = searchEndedMs + updateInterval; uint32_t compensatedStart = dueAtMs - predictedMsToGetLock; @@ -81,14 +99,18 @@ bool GPSUpdateScheduling::isUpdateDue() bool GPSUpdateScheduling::searchedTooLong() { constexpr uint32_t oneMinuteMs = 60UL * 1000UL; - constexpr uint32_t maxSearchClampMs = 15UL * oneMinuteMs; // Hard cap: 15 minutes is always too long + constexpr uint32_t maxSearchClampMs = 15UL * oneMinuteMs; // Hard cap: 15 minutes is always too long + constexpr uint32_t postFailureSearchMs = 5UL * oneMinuteMs; // Tighter dwell once we know the environment is hostile uint32_t elapsed = elapsedSearchMs(); // Anything over 15 minutes is too long, regardless of the broadcast interval. - // TODO: Make a smarter algorithm that backs off the search dwell time when not getting a lock. if (elapsed > maxSearchClampMs) return true; + // After a prior failed search, shorten the dwell + if (consecutiveFailures > 0 && elapsed > postFailureSearchMs) + return true; + uint32_t minimumOrConfiguredSecs = Default::getConfiguredOrMinimumValue(config.position.position_broadcast_secs, default_broadcast_interval_secs); uint32_t maxSearchMs = Default::getConfiguredOrDefaultMs(minimumOrConfiguredSecs, default_broadcast_interval_secs); diff --git a/src/gps/GPSUpdateScheduling.h b/src/gps/GPSUpdateScheduling.h index 64835a469..120605c4e 100644 --- a/src/gps/GPSUpdateScheduling.h +++ b/src/gps/GPSUpdateScheduling.h @@ -25,6 +25,7 @@ class GPSUpdateScheduling uint32_t searchEndedMs = 0; uint32_t searchCount = 0; uint32_t predictedMsToGetLock = 0; + uint32_t consecutiveFailures = 0; // Count of search cycles that ended without a fix; reset on lock const float weighting = 0.2; // Controls exponential smoothing of lock-times prediction. 20% weighting of "latest lock-time". }; \ No newline at end of file From b246bcd72ec6989b803da0d83d217b045303d3c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 20:52:30 -0500 Subject: [PATCH 6/6] Update libpax digest to df42474 (#10406) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/esp32-common.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index db826b3f9..91ae0017b 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -72,7 +72,7 @@ lib_deps = # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino h2zero/NimBLE-Arduino@1.4.3 # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master - https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip + https://github.com/dbinfrago/libpax/archive/df424747f9acb86ab07c5a206ded1e8e3650726a.zip # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib lewisxhe/XPowersLib@0.3.3 # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto