From c38aff7e521c88805cde46d1ec176a87992beca6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 19 Jan 2026 15:39:24 -0600 Subject: [PATCH 001/223] Add interrupt for external charge detection (#9332) Tested on Thinknode m4, m6, and T1000-e --- src/Power.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Power.cpp b/src/Power.cpp index e320f0557..cec881f83 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -723,6 +723,16 @@ bool Power::setup() runASAP = true; }, CHANGE); +#endif +#ifdef EXT_CHRG_DETECT + attachInterrupt( + EXT_CHRG_DETECT, + []() { + power->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + }, + CHANGE); #endif enabled = found; low_voltage_counter = 0; From fc268d43d04bb81857250fc0cfc38bcd7a4ddfd3 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 19 Jan 2026 16:57:21 -0600 Subject: [PATCH 002/223] Add Meshtastic exclusion flags for webserver and paxcounter in platformio.ini --- variants/esp32/chatter2/platformio.ini | 2 ++ variants/esp32/m5stack_core/platformio.ini | 2 ++ 2 files changed, 4 insertions(+) diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index 94a846bc9..4218e8503 100644 --- a/variants/esp32/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -6,6 +6,8 @@ build_flags = ${esp32_base.build_flags} -D CHATTER_2 -I variants/esp32/chatter2 + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 lib_deps = ${esp32_base.lib_deps} diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index d6c5c5b4c..4544d73b5 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -16,6 +16,8 @@ build_flags = ${esp32_base.build_flags} -I variants/esp32/m5stack_core -DM5STACK + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 -DUSER_SETUP_LOADED -DTFT_SDA_READ -DTFT_DRIVER=0x9341 From fb3bf783dd971cb1ac33ef6fcaa0eacb0a010880 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 19 Jan 2026 20:00:41 -0600 Subject: [PATCH 003/223] Implement graduated scaling for NodeInfo send timeout based on active mesh size (#9364) * Implement graduated scaling for NodeInfo send timeout based on active mesh size * Shorter timeout still needed for pubkey unkown and ad-hoc send * Update src/modules/NodeInfoModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/modules/NodeInfoModule.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 7db8b66cc..a568505ae 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -2,6 +2,7 @@ #include "Default.h" #include "MeshService.h" #include "NodeDB.h" +#include "NodeStatus.h" #include "RTC.h" #include "Router.h" #include "configuration.h" @@ -129,14 +130,17 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() LOG_DEBUG("Skip send NodeInfo > 40%% ch. util"); return NULL; } - // If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway. - if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 5 * 60 * 1000)) { - LOG_DEBUG("Skip send NodeInfo since we sent it <5min ago"); + + // Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient) + uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline()); + if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) { + LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; } else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) { + // For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago"); - ignoreRequest = true; // Mark it as ignored for MeshModule + ignoreRequest = true; return NULL; } else { ignoreRequest = false; // Don't ignore requests anymore From 3e3299f5491008732ee5568d967a2531b12ee383 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 09:07:23 +1100 Subject: [PATCH 004/223] Update meshtastic/device-ui digest to 613c095 (#9383) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 7ba2c4166..016bc1b2d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/3480b731d28b10d73414cf0dd7975bff745de8cf.zip + https://github.com/meshtastic/device-ui/archive/613c0953313bbd236f4ddc5ede447e9edf8e890a.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 6d6a0734b04cff50bf9e1836f0704dc971c8ea7b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 23 Jan 2026 15:37:16 -0600 Subject: [PATCH 005/223] Add pin sense to wake M6 on Solar Charge (#9416) --- variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index 9c7b521ef..4ce8ecdf0 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -67,4 +67,8 @@ void variant_shutdown() nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1); + + nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2); } From 6b88d37b73e83c595b92179154b13faa67dd5af3 Mon Sep 17 00:00:00 2001 From: "Justin E. Mann" Date: Sat, 24 Jan 2026 04:16:36 -0700 Subject: [PATCH 006/223] To fix the gps power rail issue on RAK 19007 when RAK12023+RAK12035 is installed (#9409) * asked claude to fix the gps power rail issue when the io slot is in use.. this fixes the gps when both the RAK12500 GPS module and the RAK12035 soil sensor modules are being used. * remove do { ... } while(0) from RESTORE_3V3_POWER() Macro * remove some comments * cleaner macro * removed more excessive comments --------- Co-authored-by: Ben Meadors --- .../Telemetry/Sensor/RAK12035Sensor.cpp | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp index ff0628cc3..626cc0e87 100644 --- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp @@ -4,6 +4,15 @@ #include "../mesh/generated/meshtastic/telemetry.pb.h" #include "RAK12035Sensor.h" +// The RAK12035 library's sensor_sleep() sets WB_IO2 (GPIO 34) LOW, which controls +// the 3.3V switched power rail (PIN_3V3_EN). This turns off power to ALL peripherals +// including GPS. We need to restore power after the library turns it off. +#ifdef PIN_3V3_EN +#define RESTORE_3V3_POWER() digitalWrite(PIN_3V3_EN, HIGH) +#else +#define RESTORE_3V3_POWER() +#endif + RAK12035Sensor::RAK12035Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RAK12035, "RAK12035") {} bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) @@ -13,7 +22,6 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) delay(100); sensor.begin(dev->address.address); - // Get sensor firmware version uint8_t data = 0; sensor.get_sensor_version(&data); if (data != 0) { @@ -21,8 +29,8 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName); status = true; sensor.sensor_sleep(); + RESTORE_3V3_POWER(); } else { - // If we reach here, it means the sensor did not initialize correctly. LOG_INFO("Init sensor: %s", sensorName); LOG_ERROR("RAK12035Sensor Init Failed"); status = false; @@ -38,8 +46,6 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) void RAK12035Sensor::setup() { - // Set the calibration values - // Reading the saved calibration values from the sensor. // TODO:: Check for and run calibration check for up to 2 additional sensors if present. uint16_t zero_val = 0; uint16_t hundred_val = 0; @@ -71,6 +77,7 @@ void RAK12035Sensor::setup() LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val); } sensor.sensor_sleep(); + RESTORE_3V3_POWER(); delay(200); LOG_INFO("Dry calibration value is %d", zero_val); LOG_INFO("Wet calibration value is %d", hundred_val); @@ -79,10 +86,6 @@ void RAK12035Sensor::setup() bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement) { // TODO:: read and send metrics for up to 2 additional soil monitors if present. - // -- how to do this.. this could get a little complex.. - // ie - 1> we combine them into an average and send that, 2> we send them as separate metrics - // ^-- these scenarios would require different handling of the metrics in the receiving end and maybe a setting in the - // device ui and an additional proto for that? measurement->variant.environment_metrics.has_soil_temperature = true; measurement->variant.environment_metrics.has_soil_moisture = true; @@ -97,6 +100,7 @@ bool RAK12035Sensor::getMetrics(meshtastic_Telemetry *measurement) success &= sensor.get_sensor_temperature(&temp); delay(200); sensor.sensor_sleep(); + RESTORE_3V3_POWER(); if (success == false) { LOG_ERROR("Failed to read sensor data"); From 04d2dd3b1c330c3b0dbedd7d36eb5be465b399ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:16:47 -0600 Subject: [PATCH 007/223] Update GxEPD2 to v1.6.6 (#9412) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/m5stack_coreink/platformio.ini | 2 +- variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini | 2 +- variants/esp32s3/esp32-s3-pico/platformio.ini | 2 +- variants/esp32s3/t-deck-pro/platformio.ini | 2 +- variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini | 2 +- variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini | 2 +- variants/nrf52840/MakePython_nRF52840_eink/platformio.ini | 2 +- variants/nrf52840/TWC_mesh_v4/platformio.ini | 2 +- variants/nrf52840/rak4631_epaper/platformio.ini | 2 +- variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/variants/esp32/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini index a4d44a15e..0a79cab28 100644 --- a/variants/esp32/m5stack_coreink/platformio.ini +++ b/variants/esp32/m5stack_coreink/platformio.ini @@ -19,7 +19,7 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.3 lib_ignore = diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index 95e909b91..f44f26006 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -11,7 +11,7 @@ upload_speed = 921600 lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.2 build_unflags = diff --git a/variants/esp32s3/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini index 8b65c3ca3..db0c038e6 100644 --- a/variants/esp32s3/esp32-s3-pico/platformio.ini +++ b/variants/esp32s3/esp32-s3-pico/platformio.ini @@ -23,6 +23,6 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.2 diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini index b2c91dcf5..aca31b599 100644 --- a/variants/esp32s3/t-deck-pro/platformio.ini +++ b/variants/esp32s3/t-deck-pro/platformio.ini @@ -29,7 +29,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip # renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328 diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini index 880871e13..093c3732d 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -12,5 +12,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini index 025e28078..5951a37a3 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini @@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS0 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) upload_protocol = nrfutil ;upload_port = /dev/ttyACM1 diff --git a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini index d823a7230..ecb630bb7 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini +++ b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini @@ -15,6 +15,6 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink ;upload_port = /dev/ttyACM4 \ No newline at end of file diff --git a/variants/nrf52840/TWC_mesh_v4/platformio.ini b/variants/nrf52840/TWC_mesh_v4/platformio.ini index e196029ec..d93f179c2 100644 --- a/variants/nrf52840/TWC_mesh_v4/platformio.ini +++ b/variants/nrf52840/TWC_mesh_v4/platformio.ini @@ -9,5 +9,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mes lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 debug_tool = jlink diff --git a/variants/nrf52840/rak4631_epaper/platformio.ini b/variants/nrf52840/rak4631_epaper/platformio.ini index bc21b7912..f0da832cb 100644 --- a/variants/nrf52840/rak4631_epaper/platformio.ini +++ b/variants/nrf52840/rak4631_epaper/platformio.ini @@ -15,7 +15,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028 melopero/Melopero RV3028@1.2.0 # renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini index d0bca377b..112ddfc29 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini @@ -17,7 +17,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.5 + zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028 melopero/Melopero RV3028@1.2.0 # renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library From a417760887cbc25bddc5e2bb9204ae1ea45ababf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:18:48 -0600 Subject: [PATCH 008/223] Update meshtastic/device-ui digest to 37ad715 (#9403) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 016bc1b2d..ee51d24ec 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/613c0953313bbd236f4ddc5ede447e9edf8e890a.zip + https://github.com/meshtastic/device-ui/archive/37ad715b76cd6ca4aa500a4a4d9740e3cdf3e3cb.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 7221fc4d4b1f66dd1bc1f52bcbeec888b59b8ddd Mon Sep 17 00:00:00 2001 From: Eric Sesterhenn Date: Sat, 24 Jan 2026 12:19:19 +0100 Subject: [PATCH 009/223] Delete unused code (#9350) * Delete unused code CryptoEngine::clearKeys() is not used in the code base, therefore this cleanup removes the code. It might give casual reviewers the impression, that keys are wiped. Since the code uses memset() which might be optimized away by the compiler, using the code might not even cause the memory to be wiped. * Update CryptoEngine.cpp Fix stray newline, this is the only thing that I can come up with that might confuse the linter. --------- Co-authored-by: Jason P --- src/mesh/CryptoEngine.cpp | 5 ----- src/mesh/CryptoEngine.h | 1 - 2 files changed, 6 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 9ca16878d..0f4d64113 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -61,11 +61,6 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey) return true; } #endif -void CryptoEngine::clearKeys() -{ - memset(public_key, 0, sizeof(public_key)); - memset(private_key, 0, sizeof(private_key)); -} /** * Encrypt a packet's payload using a key generated with Curve25519 and SHA256 diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 6bbcb3b8a..7689006ab 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -37,7 +37,6 @@ class CryptoEngine virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey); #endif - void clearKeys(); void setDHPrivateKey(uint8_t *_private_key); virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut); From 5838b26d90c37702a95f08f4f59e14a38b2bd5b5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:39:05 -0600 Subject: [PATCH 010/223] Update lewisxhe-SensorLib to v0.3.4 (#9395) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/tbeam/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index 51952457a..16a3d1845 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -32,4 +32,4 @@ lib_deps = # renovate: datasource=github-tags depName=meshtastic-st7796 packageName=meshtastic/st7796 https://github.com/meshtastic/st7796/archive/1.0.5.zip # renovate: datasource=custom.pio depName=lewisxhe-SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 From c98f134b403e4fd8c523b8e106befc8ddec7ee28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:39:22 -0600 Subject: [PATCH 011/223] Update meshtastic-esp32_https_server digest to b0f3960 (#9393) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/esp32-common.ini | 2 +- variants/esp32/esp32.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index 3ee2b9516..1ff4ce4d8 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -59,7 +59,7 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master - https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip + https://github.com/meshtastic/esp32_https_server/archive/b0f3960b3e8444563280656d88e22b5899481884.zip # 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 diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index 502d937b0..2ce9f4d22 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -17,7 +17,7 @@ lib_deps = ${environmental_extra_no_bsec.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master - https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip + https://github.com/meshtastic/esp32_https_server/archive/b0f3960b3e8444563280656d88e22b5899481884.zip # 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 From 9faf178bdcaef725f50f3a109031a12441e468be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:45:26 -0600 Subject: [PATCH 012/223] Update XPowersLib to v0.3.3 (#9354) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/esp32-common.ini | 2 +- variants/esp32/esp32.ini | 2 +- variants/esp32c6/esp32c6.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini index 1ff4ce4d8..bbbcd3cbe 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -65,7 +65,7 @@ lib_deps = # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib - https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip + https://github.com/lewisxhe/XPowersLib/archive/v0.3.3.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto rweather/Crypto@0.4.0 diff --git a/variants/esp32/esp32.ini b/variants/esp32/esp32.ini index 2ce9f4d22..20ce38fae 100644 --- a/variants/esp32/esp32.ini +++ b/variants/esp32/esp32.ini @@ -23,6 +23,6 @@ lib_deps = # renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip # renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib - https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip + https://github.com/lewisxhe/XPowersLib/archive/v0.3.3.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto rweather/Crypto@0.4.0 \ No newline at end of file diff --git a/variants/esp32c6/esp32c6.ini b/variants/esp32c6/esp32c6.ini index c1dfa4d28..9ee8591be 100644 --- a/variants/esp32c6/esp32c6.ini +++ b/variants/esp32c6/esp32c6.ini @@ -28,7 +28,7 @@ lib_deps = ${environmental_extra.lib_deps} ${radiolib_base.lib_deps} # renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib - lewisxhe/XPowersLib@0.3.2 + lewisxhe/XPowersLib@0.3.3 # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto From b627fa720ba5a259bdc0b9254faf4f9f679f31d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 05:46:46 -0600 Subject: [PATCH 013/223] Update SensorLib to v0.3.4 (#9396) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/m5stack_coreink/platformio.ini | 2 +- variants/esp32s3/t-watch-s3/platformio.ini | 2 +- variants/esp32s3/tbeam-s3-core/platformio.ini | 2 +- variants/esp32s3/tlora-pager/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini | 2 +- variants/nrf52840/nano-g2-ultra/platformio.ini | 2 +- variants/nrf52840/t-echo/platformio.ini | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/variants/esp32/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini index 0a79cab28..af1535f59 100644 --- a/variants/esp32/m5stack_coreink/platformio.ini +++ b/variants/esp32/m5stack_coreink/platformio.ini @@ -21,7 +21,7 @@ lib_deps = # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 lib_ignore = m5stack-coreink monitor_filters = esp32_exception_decoder diff --git a/variants/esp32s3/t-watch-s3/platformio.ini b/variants/esp32s3/t-watch-s3/platformio.ini index 7d7b07ff6..9c7a642b2 100644 --- a/variants/esp32s3/t-watch-s3/platformio.ini +++ b/variants/esp32s3/t-watch-s3/platformio.ini @@ -24,7 +24,7 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.7 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 # renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library adafruit/Adafruit DRV2605 Library@1.2.4 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio diff --git a/variants/esp32s3/tbeam-s3-core/platformio.ini b/variants/esp32s3/tbeam-s3-core/platformio.ini index c0a32c49c..512cf3202 100644 --- a/variants/esp32s3/tbeam-s3-core/platformio.ini +++ b/variants/esp32s3/tbeam-s3-core/platformio.ini @@ -19,7 +19,7 @@ board_check = true lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 build_flags = ${esp32s3_base.build_flags} diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 5973db1d0..ea6369f29 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -39,7 +39,7 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library lewisxhe/PCF8563_Library@1.0.1 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 # renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.0.zip # TODO renovate diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini index bf9492075..6751dd4ef 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini @@ -25,4 +25,4 @@ lib_deps = # renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM khoih-prog/nRF52_PWM@1.0.1 ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.3 + ; lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini index 413eb4fab..a31615545 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini @@ -22,4 +22,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW lib_deps = ${nrf52840_base.lib_deps} ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.3 + ; lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/nano-g2-ultra/platformio.ini b/variants/nrf52840/nano-g2-ultra/platformio.ini index 0748b7e38..7e593a49a 100644 --- a/variants/nrf52840/nano-g2-ultra/platformio.ini +++ b/variants/nrf52840/nano-g2-ultra/platformio.ini @@ -20,5 +20,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/nano-g2 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 ;upload_protocol = fs diff --git a/variants/nrf52840/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini index 9a66890a7..4acd70b02 100644 --- a/variants/nrf52840/t-echo/platformio.ini +++ b/variants/nrf52840/t-echo/platformio.ini @@ -32,7 +32,7 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 ;upload_protocol = fs [env:t-echo-inkhud] @@ -53,4 +53,4 @@ lib_deps = ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 From b312f226b4cdf279e296d3958aac60b23ae82301 Mon Sep 17 00:00:00 2001 From: phaseloop <90922095+phaseloop@users.noreply.github.com> Date: Sat, 24 Jan 2026 14:22:01 +0200 Subject: [PATCH 014/223] Cut NRF52 bluetooth power usage (#8992) * Improve NRF52 bluetooth power efficiency * test T114 bad LFXO * T1000 test * force BLE param negotiation * stash * NRF52 bluetooth small cleanup * fix potential connectivity issues * lower BLE min interval to make iOS happy * remove slave latency negotation * add BLE issue comment * code format * Revert "code format" This reverts commit 1f92b09d08cde52cf61f115a6335dfce12ad9ffd. * remove LFCLK debug info * Fix --------- Co-authored-by: Ben Meadors --- src/platform/nrf52/NRF52Bluetooth.cpp | 35 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index d41c7ba0e..6a552f236 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -119,7 +119,7 @@ void startAdv(void) Bluefruit.Advertising.addService(meshBleService); /* Start Advertising * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Interval: fast mode = 20 ms, slow mode = 417,5 ms * - Timeout for fast mode is 30 seconds * - Start(timeout) with timeout = 0 will advertise forever (until connected) * @@ -127,7 +127,7 @@ void startAdv(void) * 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.setInterval(32, 668); // 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. FIXME, we should stop advertising after X } @@ -243,10 +243,8 @@ int NRF52Bluetooth::getRssi() // Valid BLE TX power levels as per nRF52840 Product Specification are: "-20 to +8 dBm TX power, configurable in 4 dB steps". // See https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html -#define VALID_BLE_TX_POWER(x) \ - ((x) == -20 || (x) == -16 || (x) == -12 || \ - (x) == -8 || (x) == -4 || (x) == 0 || \ - (x) == 4 || (x) == 8) +#define VALID_BLE_TX_POWER(x) \ + ((x) == -20 || (x) == -16 || (x) == -12 || (x) == -8 || (x) == -4 || (x) == 0 || (x) == 4 || (x) == 8) void NRF52Bluetooth::setup() { @@ -283,6 +281,29 @@ void NRF52Bluetooth::setup() // Set the connect/disconnect callback handlers Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); + + // Do not change Slave Latency to value other than 0 !!! + // There is probably a bug in SoftDevice + certain Apple iOS versions being + // brain damaged causing connectivity problems. + + // On one side it seems SoftDevice is using SlaveLatency value even + // if connection parameter negotation failed and phone sees it as connectivity errors. + + // On the other hand Apple can randomly refuse any parameter negotiation and shutdown connection + // even if you meet Apple Developer Guidelines for BLE devices. Because f* you, that's why. + + // While this API call sets preferred connection parameters (PPCP) - many phones ignore it (yeah) and it seems SoftDevice + // will try to renegotiate connection parameters based on those values after phone connection. + // So those are relatively safe values so Apple braindead firmware won't get angry and at least we may try + // to negotiate some longer connection interval to save battery. + + // See https://github.com/meshtastic/firmware/pull/8858 for measurements. We are dealing with microamp savings anyway so not + // worth dying on a hill here. + + Bluefruit.Periph.setConnSlaveLatency(0); + // 1.25 ms units - so min, max is 15, 100 ms range. + Bluefruit.Periph.setConnInterval(12, 80); + #ifndef BLE_DFU_SECURE bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bledfu.begin(); // Install the DFU helper @@ -311,7 +332,7 @@ void NRF52Bluetooth::setup() void NRF52Bluetooth::resumeAdvertising() { Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); } From 6cff13623f93880e4cd5537a406b77f69bf8cbe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?U=C4=9Fur=20ALTINSOY?= <48494791+uguraltinsoy@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:38:07 +0300 Subject: [PATCH 015/223] Added Minimesh variant (#9289) * Minimesh Lite Added * Add Minimesh Lite NRF * Added board_level = extra * Fix formatting and optimize image for Minimesh Lite * Change image * The image has been deleted. --------- Co-authored-by: Ben Meadors --- boards/minimesh_lite.json | 50 +++++++++ .../nrf52840/dls_Minimesh_Lite/platformio.ini | 9 ++ .../nrf52840/dls_Minimesh_Lite/variant.cpp | 38 +++++++ variants/nrf52840/dls_Minimesh_Lite/variant.h | 104 ++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 boards/minimesh_lite.json create mode 100644 variants/nrf52840/dls_Minimesh_Lite/platformio.ini create mode 100644 variants/nrf52840/dls_Minimesh_Lite/variant.cpp create mode 100644 variants/nrf52840/dls_Minimesh_Lite/variant.h diff --git a/boards/minimesh_lite.json b/boards/minimesh_lite.json new file mode 100644 index 000000000..0b8f0b909 --- /dev/null +++ b/boards/minimesh_lite.json @@ -0,0 +1,50 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DMINIMESH_LITE -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"] + ], + "usb_product": "Minimesh Lite", + "mcu": "nrf52840", + "variant": "dls_Minimesh_Lite", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Minimesh Lite", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://deeplabstudio.com", + "vendor": "Deeplab Studio" +} diff --git a/variants/nrf52840/dls_Minimesh_Lite/platformio.ini b/variants/nrf52840/dls_Minimesh_Lite/platformio.ini new file mode 100644 index 000000000..763ff5477 --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/platformio.ini @@ -0,0 +1,9 @@ +[env:minimesh_lite] +extends = nrf52840_base +board = minimesh_lite +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/dls_Minimesh_Lite + -DPRIVATE_HW +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/dls_Minimesh_Lite> +debug_tool = jlink diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.cpp b/variants/nrf52840/dls_Minimesh_Lite/variant.cpp new file mode 100644 index 000000000..5869ed1d4 --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.cpp @@ -0,0 +1,38 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.h b/variants/nrf52840/dls_Minimesh_Lite/variant.h new file mode 100644 index 000000000..f5163619b --- /dev/null +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.h @@ -0,0 +1,104 @@ +#ifndef _VARIANT_MINIMESH_LITE_ +#define _VARIANT_MINIMESH_LITE_ + +#define VARIANT_MCK (64000000ul) +#define USE_LFRC + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MINIMESH_LITE + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +#define PIN_3V3_EN (0 + 13) // P0.13 + +// Analog pins +#define BATTERY_PIN (0 + 31) // P0.31 Battery ADC +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define VBAT_MV_PER_LSB (0.73242188F) +#define VBAT_DIVIDER (0.6F) +#define VBAT_DIVIDER_COMP (1.73) +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER VBAT_DIVIDER_COMP +#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x) + +// WIRE IC AND IIC PINS +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (32 + 4) +#define PIN_WIRE_SCL (0 + 11) + +// LED +#define PIN_LED1 (0 + 15) +#define LED_BUILTIN PIN_LED1 +// Actually red +#define LED_BLUE PIN_LED1 +#define LED_STATE_ON 1 + +// Button +#define BUTTON_PIN (32 + 0) + +// GPS +#define GPS_TX_PIN (0 + 20) +#define GPS_RX_PIN (0 + 22) + +#define PIN_GPS_EN (0 + 24) +#define GPS_UBLOX +// define GPS_DEBUG + +// UART interfaces +#define PIN_SERIAL1_TX GPS_TX_PIN +#define PIN_SERIAL1_RX GPS_RX_PIN + +#define PIN_SERIAL2_RX (0 + 6) +#define PIN_SERIAL2_TX (0 + 8) + +// Serial interfaces +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (0 + 2) +#define PIN_SPI_MOSI (32 + 15) +#define PIN_SPI_SCK (32 + 11) + +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_SCK PIN_SPI_SCK +#define LORA_CS (32 + 13) + +// LORA MODULES +#define USE_LLCC68 +#define USE_SX1262 +#define USE_SX1268 + +// SX126X CONFIG +#define SX126X_CS (32 + 13) +#define SX126X_DIO1 (0 + 10) +#define SX126X_DIO2_AS_RF_SWITCH + +#define SX126X_BUSY (0 + 29) +#define SX126X_RESET (0 + 9) +#define SX126X_RXEN (0 + 17) +#define SX126X_TXEN RADIOLIB_NC + +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL + +#ifdef __cplusplus +} +#endif + +#endif // _VARIANT_MINIMESH_LITE_ From 57a3ff8dfcc7b2b4f766de224cc80376e7332564 Mon Sep 17 00:00:00 2001 From: phaseloop <90922095+phaseloop@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:39:03 +0200 Subject: [PATCH 016/223] NRF52 - power management improvements (#9211) * minor NRF52 test cleanup * detect USB power input on ProMicro boards * prevent booting on power failure detection * introduce PowerHAL layer * powerHAL basic implementation for NRF52 * prevent data saves on low power * remove comment * Update src/platform/nrf52/main-nrf52.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/power/PowerHAL.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/main.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Merge missing voltage threshold comparison * add missing variable * add missing function declaration * remove debug strings --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors --- src/Power.cpp | 194 ++++++++++++++++-------------- src/main.cpp | 46 +++++++ src/mesh/NodeDB.cpp | 49 ++++++++ src/platform/nrf52/architecture.h | 19 +++ src/platform/nrf52/main-nrf52.cpp | 144 ++++++++++++++++++---- src/power/PowerHAL.cpp | 19 +++ src/power/PowerHAL.h | 26 ++++ 7 files changed, 384 insertions(+), 113 deletions(-) create mode 100644 src/power/PowerHAL.cpp create mode 100644 src/power/PowerHAL.h diff --git a/src/Power.cpp b/src/Power.cpp index cec881f83..b2a4ddaaf 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -1,11 +1,14 @@ /** * @file Power.cpp - * @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality - * of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The - * Power class is used by the main device class to manage power-related functionality. + * @brief This file contains the implementation of the Power class, which is + * responsible for managing power-related functionality of the device. It + * includes battery level sensing, power management unit (PMU) control, and + * power state machine management. The Power class is used by the main device + * class to manage power-related functionality. * - * The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes - * the battery voltage is attached via a voltage-divider to an analog input. + * The file also includes implementations of various battery level sensors, such + * as the AnalogBatteryLevel class, which assumes the battery voltage is + * attached via a voltage-divider to an analog input. * * This file is part of the Meshtastic project. * For more information, see: https://meshtastic.org/ @@ -19,6 +22,7 @@ #include "configuration.h" #include "main.h" #include "meshUtils.h" +#include "power/PowerHAL.h" #include "sleep.h" #if defined(ARCH_PORTDUINO) @@ -171,22 +175,12 @@ Power *power; using namespace meshtastic; -#ifndef AREF_VOLTAGE -#if defined(ARCH_NRF52) -/* - * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4, - * 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels. - * - * External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning - * VDD/4, VDD/2 or VDD for the ADC levels. - * - * Default settings are internal reference with 1/6 gain (GND..3.6V ADC range) - */ -#define AREF_VOLTAGE 3.6 -#else +// NRF52 has AREF_VOLTAGE defined in architecture.h but +// make sure it's included. If something is wrong with NRF52 +// definition - compilation will fail on missing definition +#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52) #define AREF_VOLTAGE 3.3 #endif -#endif /** * If this board has a battery level sensor, set this to a valid implementation @@ -233,7 +227,8 @@ static void battery_adcDisable() #endif /** - * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input + * A simple battery level sensor that assumes the battery voltage is attached + * via a voltage-divider to an analog input */ class AnalogBatteryLevel : public HasBatteryLevel { @@ -311,7 +306,8 @@ class AnalogBatteryLevel : public HasBatteryLevel #ifndef BATTERY_SENSE_SAMPLES #define BATTERY_SENSE_SAMPLES \ - 15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment. + 15 // Set the number of samples, it has an effect of increasing sensitivity in + // complex electromagnetic environment. #endif #ifdef BATTERY_PIN @@ -341,7 +337,8 @@ class AnalogBatteryLevel : public HasBatteryLevel battery_adcDisable(); if (!initial_read_done) { - // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct + // Flush the smoothing filter with an ADC reading, if the reading is + // plausibly correct if (scaled > last_read_value) last_read_value = scaled; initial_read_done = true; @@ -350,8 +347,8 @@ class AnalogBatteryLevel : public HasBatteryLevel last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF } - // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) - // (last_read_value)); + // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", + // BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value)); } return last_read_value; #endif // BATTERY_PIN @@ -420,7 +417,8 @@ class AnalogBatteryLevel : public HasBatteryLevel /** * return true if there is a battery installed in this unit */ - // if we have a integrated device with a battery, we can assume that the battery is always connected + // if we have a integrated device with a battery, we can assume that the + // battery is always connected #ifdef BATTERY_IMMUTABLE virtual bool isBatteryConnect() override { return true; } #elif defined(ADC_V) @@ -441,10 +439,10 @@ class AnalogBatteryLevel : public HasBatteryLevel virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; } #endif - /// If we see a battery voltage higher than physics allows - assume charger is pumping - /// in power - /// On some boards we don't have the power management chip (like AXPxxxx) - /// so we use EXT_PWR_DETECT GPIO pin to detect external power source + /// If we see a battery voltage higher than physics allows - assume charger is + /// pumping in power On some boards we don't have the power management chip + /// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power + /// source virtual bool isVbusIn() override { #ifdef EXT_PWR_DETECT @@ -461,8 +459,12 @@ class AnalogBatteryLevel : public HasBatteryLevel } // if it's not HIGH - check the battery #endif -#elif defined(MUZI_BASE) - return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; + +// technically speaking this should work for all(?) NRF52 boards +// but needs testing across multiple devices. NRF52 USB would not even work if +// VBUS was not properly connected and detected by the CPU +#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO) + return powerHAL_isVBUSConnected(); #endif return getBattVoltage() > chargingVolt; } @@ -485,8 +487,9 @@ class AnalogBatteryLevel : public HasBatteryLevel #else #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) if (hasINA()) { - // get current flow from INA sensor - negative value means power flowing into the battery - // default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD + // get current flow from INA sensor - negative value means power flowing + // into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT + // RESISTOR <--> INA_VIN- <--> LOAD LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address); #if defined(INA_CHARGING_DETECTION_INVERT) return getINACurrent() > 0; @@ -502,8 +505,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } private: - /// If we see a battery voltage higher than physics allows - assume charger is pumping - /// in power + /// If we see a battery voltage higher than physics allows - assume charger is + /// pumping in power /// For heltecs with no battery connected, the measured voltage is 2204, so // need to be higher than that, in this case is 2500mV (3000-500) @@ -512,7 +515,8 @@ class AnalogBatteryLevel : public HasBatteryLevel const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS; // Start value from minimum voltage for the filter to not start from 0 // that could trigger some events. - // This value is over-written by the first ADC reading, it the voltage seems reasonable. + // This value is over-written by the first ADC reading, it the voltage seems + // reasonable. bool initial_read_done = false; float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); uint32_t last_read_time_ms = 0; @@ -654,7 +658,8 @@ bool Power::analogInit() #ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3 else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) { - LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse"); + LOG_INFO("ADC config based on Two Point values and fitting curve " + "coefficients stored in eFuse"); } #endif else { @@ -662,13 +667,7 @@ bool Power::analogInit() } #endif // ARCH_ESP32 -#ifdef ARCH_NRF52 -#ifdef VBAT_AR_INTERNAL - analogReference(VBAT_AR_INTERNAL); -#else - analogReference(AR_INTERNAL); // 3.6V -#endif -#endif // ARCH_NRF52 + // NRF52 ADC init moved to powerHAL_init in nrf52 platform #ifndef ARCH_ESP32 analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); @@ -779,7 +778,8 @@ void Power::reboot() HAL_NVIC_SystemReset(); #else rebootAtMsec = -1; - LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied"); + LOG_WARN("FIXME implement reboot for this platform. Note that some settings " + "require a restart to be applied"); #endif } @@ -789,9 +789,12 @@ void Power::shutdown() #if HAS_SCREEN if (screen) { #ifdef T_DECK_PRO - screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button + screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", + 0); // T-Deck Pro has no power button #elif defined(USE_EINK) - screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen + screen->showSimpleBanner("Shutting Down...", + 2250); // dismiss after 3 seconds to avoid the + // banner on the sleep screen #else screen->showSimpleBanner("Shutting Down...", 0); // stays on screen #endif @@ -830,7 +833,8 @@ void Power::readPowerStatus() int32_t batteryVoltageMv = -1; // Assume unknown int8_t batteryChargePercent = -1; OptionalBool usbPowered = OptUnknown; - OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time + OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM + // code doesn't run every time OptionalBool isChargingNow = OptUnknown; if (batteryLevel) { @@ -843,9 +847,10 @@ void Power::readPowerStatus() if (batteryLevel->getBatteryPercent() >= 0) { batteryChargePercent = batteryLevel->getBatteryPercent(); } else { - // If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error - // In that case, we compute an estimate of the charge percent based on open circuit voltage table defined - // in power.h + // If the AXP192 returns a percentage less than 0, the feature is either + // not supported or there is an error In that case, we compute an + // estimate of the charge percent based on open circuit voltage table + // defined in power.h batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) / ((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))), 0, 100); @@ -853,12 +858,12 @@ void Power::readPowerStatus() } } -// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass -// (which shares a superclass with the BatteryLevel stuff) -// that just provides a few methods. But in the interest of fixing this bug I'm going to follow current -// practice. -#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect - // changes. +// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way +// better instead to make a Nrf52IsUsbPowered subclass (which shares a +// superclass with the BatteryLevel stuff) that just provides a few methods. But +// in the interest of fixing this bug I'm going to follow current practice. +#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates + // the power states. Takes 20 seconds or so to detect changes. nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); // LOG_DEBUG("NRF Power %d", nrf_usb_state); @@ -932,8 +937,9 @@ void Power::readPowerStatus() #endif - // If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in - // a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. + // If we have a battery at all and it is less than 0%, force deep sleep if we + // have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage + // is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough. // if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) { @@ -955,8 +961,8 @@ int32_t Power::runOnce() readPowerStatus(); #ifdef HAS_PMU - // WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll - // the IRQ status by reading the registers over I2C + // WE no longer use the IRQ line to wake the CPU (due to false wakes from + // sleep), but we do poll the IRQ status by reading the registers over I2C if (PMU) { PMU->getIrqStatus(); @@ -998,7 +1004,8 @@ int32_t Power::runOnce() PMU->clearIrqStatus(); } #endif - // Only read once every 20 seconds once the power status for the app has been initialized + // Only read once every 20 seconds once the power status for the app has been + // initialized return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME; } @@ -1006,10 +1013,12 @@ int32_t Power::runOnce() * Init the power manager chip * * axp192 power - DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the - axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this - on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of - days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS + DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose + comms to the axp192 because the OLED and the axp192 share the same i2c bus, + instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> + ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the + tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can + not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS * */ bool Power::axpChipInit() @@ -1054,9 +1063,10 @@ bool Power::axpChipInit() if (!PMU) { /* - * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time. - * In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once, - * if there are multiple devices sharing the bus. + * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will + * be called at the same time. In order not to affect other devices, if the + * initialization of the PMU fails, Wire needs to be re-initialized once, if + * there are multiple devices sharing the bus. * * */ #ifndef PMU_USE_WIRE1 w->begin(I2C_SDA, I2C_SCL); @@ -1073,8 +1083,8 @@ bool Power::axpChipInit() PMU->enablePowerOutput(XPOWERS_LDO2); // oled module power channel, - // disable it will cause abnormal communication between boot and AXP power supply, - // do not turn it off + // disable it will cause abnormal communication between boot and AXP power + // supply, do not turn it off PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); // enable oled power PMU->enablePowerOutput(XPOWERS_DCDC1); @@ -1101,7 +1111,8 @@ bool Power::axpChipInit() PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); } else if (PMU->getChipModel() == XPOWERS_AXP2101) { - /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/ + /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it + * uses an AXP2101 power chip*/ if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { // Unuse power channel PMU->disablePowerOutput(XPOWERS_DCDC2); @@ -1136,8 +1147,8 @@ bool Power::axpChipInit() // t-beam s3 core /** * gnss module power channel - * The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during - * initialization + * The default ALDO4 is off, you need to turn on the GNSS power first, + * otherwise it will be invalid during initialization */ PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); PMU->enablePowerOutput(XPOWERS_ALDO4); @@ -1187,7 +1198,8 @@ bool Power::axpChipInit() // disable all axp chip interrupt PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); - // Set the constant current charging current of AXP2101, temporarily use 500mA by default + // Set the constant current charging current of AXP2101, temporarily use + // 500mA by default PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); // Set up the charging voltage @@ -1253,11 +1265,12 @@ bool Power::axpChipInit() PMU->getPowerChannelVoltage(XPOWERS_BLDO2)); } -// We can safely ignore this approach for most (or all) boards because MCU turned off -// earlier than battery discharged to 2.6V. +// We can safely ignore this approach for most (or all) boards because MCU +// turned off earlier than battery discharged to 2.6V. // -// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with -// battery voltage measurement. Probably it sometimes drops to low values. +// Unfortunately for now we can't use this killswitch for RAK4630-based boards +// because they have a bug with battery voltage measurement. Probably it +// sometimes drops to low values. #ifndef RAK4630 // Set PMU shutdown voltage at 2.6V to maximize battery utilization PMU->setSysPowerDownVoltage(2600); @@ -1276,10 +1289,12 @@ bool Power::axpChipInit() attachInterrupt( PMU_IRQ, [] { pmu_irq = true; }, FALLING); - // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is - // no battery also it could cause inadvertent waking from light sleep just because the battery filled - // we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed - // we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus + // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ + // because it occurs repeatedly while there is no battery also it could cause + // inadvertent waking from light sleep just because the battery filled we + // don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while + // no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we + // don't have anything hooked to vbus PMU->enableIRQ(pmuIrqMask); PMU->clearIrqStatus(); @@ -1395,8 +1410,8 @@ class LipoCharger : public HasBatteryLevel bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR); if (result) { LOG_INFO("PPM BQ25896 init succeeded"); - // Set the minimum operating voltage. Below this voltage, the PPM will protect - // PPM->setSysPowerDownVoltage(3100); + // Set the minimum operating voltage. Below this voltage, the PPM will + // protect PPM->setSysPowerDownVoltage(3100); // Set input current limit, default is 500mA // PPM->setInputCurrentLimit(800); @@ -1419,7 +1434,8 @@ class LipoCharger : public HasBatteryLevel PPM->enableMeasure(); // Turn on charging function - // If there is no battery connected, do not turn on the charging function + // If there is no battery connected, do not turn on the charging + // function PPM->enableCharge(); } else { LOG_WARN("PPM BQ25896 init failed"); @@ -1454,7 +1470,8 @@ class LipoCharger : public HasBatteryLevel virtual int getBatteryPercent() override { return -1; - // return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated + // return bq->getChargePercent(); // don't use BQ27220 for battery percent, + // it is not calibrated } /** @@ -1576,7 +1593,8 @@ bool Power::meshSolarInit() #else /** - * The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel + * The meshSolar battery level sensor is unavailable - default to + * AnalogBatteryLevel */ bool Power::meshSolarInit() { diff --git a/src/main.cpp b/src/main.cpp index c1096a240..ea114ea34 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" +#include "power/PowerHAL.h" #include "FSCommon.h" #include "Led.h" @@ -332,6 +333,43 @@ __attribute__((weak, noinline)) bool loopCanSleep() void lateInitVariant() __attribute__((weak)); void lateInitVariant() {} +// NRF52 (and probably other platforms) can report when system is in power failure mode +// (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc). +// For example NRF52 will prevent any flash writes in that case automatically +// (but it causes issues we need to handle). +// This detection is independent from whatever ADC or dividers used in Meshtastic +// boards and is internal to chip. + +// we use powerHAL layer to get this info and delay booting until power level is safe + +// wait until power level is safe to continue booting (to avoid bootloops) +// blink user led in 3 flashes sequence to indicate what is happening +void waitUntilPowerLevelSafe() +{ + +#ifdef LED_PIN + pinMode(LED_PIN, OUTPUT); +#endif + + while (powerHAL_isPowerLevelSafe() == false) { + +#ifdef LED_PIN + + // 3x: blink for 300 ms, pause for 300 ms + + for (int i = 0; i < 3; i++) { + digitalWrite(LED_PIN, LED_STATE_ON); + delay(300); + digitalWrite(LED_PIN, LED_STATE_OFF); + delay(300); + } +#endif + + // sleep for 2s + delay(2000); + } +} + /** * Print info as a structured log message (for automated log processing) */ @@ -342,6 +380,14 @@ void printInfo() #ifndef PIO_UNIT_TESTING void setup() { + + // initialize power HAL layer as early as possible + powerHAL_init(); + + // prevent booting if device is in power failure mode + // boot sequence will follow when battery level raises to safe mode + waitUntilPowerLevelSafe(); + #if defined(R1_NEO) pinMode(DCDC_EN_HOLD, OUTPUT); digitalWrite(DCDC_EN_HOLD, HIGH); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 375bc76e3..2a3294086 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #ifdef ARCH_ESP32 @@ -1418,6 +1419,14 @@ void NodeDB::loadFromDisk() bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool fullAtomic) { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveProto() on unsafe device power level."); + return false; + } + bool okay = false; #ifdef FSCom auto f = SafeFile(filename, fullAtomic); @@ -1444,6 +1453,14 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_ bool NodeDB::saveChannelsToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveChannelsToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1454,6 +1471,14 @@ bool NodeDB::saveChannelsToDisk() bool NodeDB::saveDeviceStateToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveDeviceStateToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1466,6 +1491,14 @@ bool NodeDB::saveDeviceStateToDisk() bool NodeDB::saveNodeDatabaseToDisk() { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveNodeDatabaseToDisk() on unsafe device power level."); + return false; + } + #ifdef FSCom spiLock->lock(); FSCom.mkdir("/prefs"); @@ -1478,6 +1511,14 @@ bool NodeDB::saveNodeDatabaseToDisk() bool NodeDB::saveToDiskNoRetry(int saveWhat) { + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveToDiskNoRetry() on unsafe device power level."); + return false; + } + bool success = true; #ifdef FSCom spiLock->lock(); @@ -1533,6 +1574,14 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) bool NodeDB::saveToDisk(int saveWhat) { LOG_DEBUG("Save to disk %d", saveWhat); + + // do not try to save anything if power level is not safe. In many cases flash will be lock-protected + // and all writes will fail anyway. Device should be sleeping at this point anyway. + if (!powerHAL_isPowerLevelSafe()) { + LOG_ERROR("Error: trying to saveToDisk() on unsafe device power level."); + return false; + } + bool success = saveToDiskNoRetry(saveWhat); if (!success) { diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 7734c0020..d1965f03e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -5,6 +5,25 @@ // // defaults for NRF52 architecture // + +/* + * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4, + * 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels. + * + * External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning + * VDD/4, VDD/2 or VDD for the ADC levels. + * + * Default settings are internal reference with 1/6 gain (GND..3.6V ADC range) + * Some variants overwrite it. + */ +#ifndef AREF_VOLTAGE +#define AREF_VOLTAGE 3.6 +#endif + +#ifndef BATTERY_SENSE_RESOLUTION_BITS +#define BATTERY_SENSE_RESOLUTION_BITS 10 +#endif + #ifndef HAS_BLUETOOTH #define HAS_BLUETOOTH 1 #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 472107229..2068fe2a7 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -9,12 +9,12 @@ #define NRFX_WDT_ENABLED 1 #define NRFX_WDT0_ENABLED 1 #define NRFX_WDT_CONFIG_NO_IRQ 1 -#include -#include - +#include "nrfx_power.h" #include #include #include +#include +#include #include // #include #include "NodeDB.h" @@ -23,6 +23,7 @@ #include "main.h" #include "meshUtils.h" #include "power.h" +#include #include @@ -30,6 +31,21 @@ #include "BQ25713.h" #endif +// WARNING! THRESHOLD + HYSTERESIS should be less than regulated VDD voltage - which depends on board +// and is 3.0 or 3.3V. Also VDD likes to read values like 2.9999 so make sure you account for that +// otherwise board will not boot at all. Before you modify this part - please triple read NRF52840 power design +// section in datasheet and you understand how REG0 and REG1 regulators work together. +#ifndef SAFE_VDD_VOLTAGE_THRESHOLD +#define SAFE_VDD_VOLTAGE_THRESHOLD 2.7 +#endif + +// hysteresis value +#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_HYST +#define SAFE_VDD_VOLTAGE_THRESHOLD_HYST 0.2 +#endif + +uint16_t getVDDVoltage(); + // Weak empty variant initialization function. // May be redefined by variant files. void variant_shutdown() __attribute__((weak)); @@ -38,12 +54,95 @@ void variant_shutdown() {} static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0); static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main; +// This is a public global so that the debugger can set it to false automatically from our gdbinit +// @phaseloop comment: most part of codebase, including filesystem flash driver depend on softdevice +// methods so disabling it may actually crash thing. Proceed with caution. + +bool useSoftDevice = true; // Set to false for easier debugging + static inline void debugger_break(void) { __asm volatile("bkpt #0x01\n\t" "mov pc, lr\n\t"); } +// PowerHAL NRF52 specific function implementations +bool powerHAL_isVBUSConnected() +{ + return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk; +} + +bool powerHAL_isPowerLevelSafe() +{ + + static bool powerLevelSafe = true; + + uint16_t threshold = SAFE_VDD_VOLTAGE_THRESHOLD * 1000; // convert V to mV + uint16_t hysteresis = SAFE_VDD_VOLTAGE_THRESHOLD_HYST * 1000; + + if (powerLevelSafe) { + if (getVDDVoltage() < threshold) { + powerLevelSafe = false; + } + } else { + // power level is only safe again when it raises above threshold + hysteresis + if (getVDDVoltage() >= (threshold + hysteresis)) { + powerLevelSafe = true; + } + } + + return powerLevelSafe; +} + +void powerHAL_platformInit() +{ + + // Enable POF power failure comparator. It will prevent writing to NVMC flash when supply voltage is too low. + // Set to some low value as last resort - powerHAL_isPowerLevelSafe uses different method and should manage proper node + // behaviour on its own. + + // POFWARN is pretty useless for node power management because it triggers only once and clearing this event will not + // re-trigger it again until voltage rises to safe level and drops again. So we will use SAADC routed to VDD to read safely + // voltage. + + // @phaseloop: I disable POFCON for now because it seems to be unreliable or buggy. Even when set at 2.0V it + // triggers below 2.8V and corrupts data when pairing bluetooth - because it prevents filesystem writes and + // adafruit BLE library triggers lfs_assert which reboots node and formats filesystem. + // I did experiments with bench power supply and no matter what is set to POFCON, it always triggers right below + // 2.8V. I compared raw registry values with datasheet. + + NRF_POWER->POFCON = + ((POWER_POFCON_THRESHOLD_V22 << POWER_POFCON_THRESHOLD_Pos) | (POWER_POFCON_POF_Enabled << POWER_POFCON_POF_Pos)); + + // remember to always match VBAT_AR_INTERNAL with AREF_VALUE in variant definition file +#ifdef VBAT_AR_INTERNAL + analogReference(VBAT_AR_INTERNAL); +#else + analogReference(AR_INTERNAL); // 3.6V +#endif +} + +// get VDD voltage (in millivolts) +uint16_t getVDDVoltage() +{ + // we use the same values as regular battery read so there is no conflict on SAADC + analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); + + // VDD range on NRF52840 is 1.8-3.3V so we need to remap analog reference to 3.6V + // let's hope battery reading runs in same task and we don't have race condition + analogReference(AR_INTERNAL); + + uint16_t vddADCRead = analogReadVDD(); + float voltage = ((1000 * 3.6) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vddADCRead; + +// restore default battery reading reference +#ifdef VBAT_AR_INTERNAL + analogReference(VBAT_AR_INTERNAL); +#endif + + return voltage; +} + bool loopCanSleep() { // turn off sleep only while connected via USB @@ -72,22 +171,6 @@ void getMacAddr(uint8_t *dmac) dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack } -static void initBrownout() -{ - auto vccthresh = POWER_POFCON_THRESHOLD_V24; - - auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled); - assert(err_code == NRF_SUCCESS); - - err_code = sd_power_pof_threshold_set(vccthresh); - assert(err_code == NRF_SUCCESS); - - // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice -} - -// This is a public global so that the debugger can set it to false automatically from our gdbinit -bool useSoftDevice = true; // Set to false for easier debugging - #if !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { @@ -106,7 +189,6 @@ void setBluetoothEnable(bool enable) if (!initialized) { nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->startDisabled(); - initBrownout(); initialized = true; } return; @@ -120,9 +202,6 @@ void setBluetoothEnable(bool enable) LOG_DEBUG("Init NRF52 Bluetooth"); nrf52Bluetooth = new NRF52Bluetooth(); nrf52Bluetooth->setup(); - - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); } // Already setup, apparently else @@ -192,9 +271,24 @@ extern "C" void lfs_assert(const char *reason) delay(500); // Give the serial port a bit of time to output that last message. // Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set // NRF_POWER->GPREGRET directly. - if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { - NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + + // TODO: this will/can crash CPU if bluetooth stack is not compiled in or bluetooth is not initialized + // (regardless if enabled or disabled) - as there is no live SoftDevice stack + // implement "safe" functions detecting softdevice stack state and using proper method to set registers + + // do not set GPREGRET if POFWARN is triggered because it means lfs_assert reports flash undervoltage protection + // and not data corruption. Reboot is fine as boot procedure will wait until power level is safe again + + if (!NRF_POWER->EVENTS_POFWARN) { + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && + sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { + NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + } } + + // TODO: this should not be done when SoftDevice is enabled as device will not boot back on soft reset + // as some data is retained in RAM which will prevent re-enabling bluetooth stack + // Google what Nordic has to say about NVIC_* + SoftDevice NVIC_SystemReset(); } diff --git a/src/power/PowerHAL.cpp b/src/power/PowerHAL.cpp new file mode 100644 index 000000000..0a8d5f10b --- /dev/null +++ b/src/power/PowerHAL.cpp @@ -0,0 +1,19 @@ + +#include "PowerHAL.h" + +void powerHAL_init() +{ + return powerHAL_platformInit(); +} + +__attribute__((weak, noinline)) void powerHAL_platformInit() {} + +__attribute__((weak, noinline)) bool powerHAL_isPowerLevelSafe() +{ + return true; +} + +__attribute__((weak, noinline)) bool powerHAL_isVBUSConnected() +{ + return false; +} diff --git a/src/power/PowerHAL.h b/src/power/PowerHAL.h new file mode 100644 index 000000000..318b06810 --- /dev/null +++ b/src/power/PowerHAL.h @@ -0,0 +1,26 @@ + +/* + +Power Hardware Abstraction Layer. Set of API calls to offload power management, measurements, reboots, etc +to the platform and variant code to avoid #ifdef spaghetti hell and limitless device-based edge cases +in the main firmware code + +Functions declared here (with exception of powerHAL_init) should be defined in platform specific codebase. +Default function body does usually nothing. + +*/ + +// Initialize HAL layer. Call it as early as possible during device boot +// do not overwrite it as it's not declared with "weak" attribute. +void powerHAL_init(); + +// platform specific init code if needed to be run early on boot +void powerHAL_platformInit(); + +// Return true if current battery level is safe for device operation (for example flash writes). +// This should be reported by power failure comparator (NRF52) or similar circuits on other platforms. +// Do not use battery ADC as improper ADC configuration may prevent device from booting. +bool powerHAL_isPowerLevelSafe(); + +// return if USB voltage is connected +bool powerHAL_isVBUSConnected(); From 8894a0b71169f980e06b3131b290483a1bcf5f47 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 25 Jan 2026 18:02:26 -0600 Subject: [PATCH 017/223] Consolidate LoRa params / preset logic and fix display of preset values (#9413) * Consolidate LoRa params / preset logic and fix display of preset values * Move preset coercion logic to RadioInterface * Fix some warnings * Fix warnings * STM32 fix * Add unit tests * Update src/mesh/RadioInterface.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/gps/RTC.cpp | 9 +-- src/gps/RTC.h | 2 +- src/graphics/draw/MenuHandler.cpp | 48 +------------- src/graphics/draw/MenuHandler.h | 2 +- src/mesh/MeshRadio.h | 97 ++++++++++++++++++++++++++++- src/mesh/NodeDB.cpp | 8 +++ src/mesh/RadioInterface.cpp | 92 +++++++++------------------ src/mesh/RadioInterface.h | 9 +++ test/test_radio/test_main.cpp | 100 ++++++++++++++++++++++++++++++ 9 files changed, 252 insertions(+), 115 deletions(-) create mode 100644 test/test_radio/test_main.cpp diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 25cd3ceff..ad26b55a4 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -397,7 +397,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local) return (currentQuality >= minQuality) ? getTime(local) : 0; } -time_t gm_mktime(struct tm *tm) +time_t gm_mktime(const struct tm *tm) { #if !MESHTASTIC_EXCLUDE_TZ time_t result = 0; @@ -413,8 +413,8 @@ time_t gm_mktime(struct tm *tm) days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400); // Now, within this tm->year, compute the days *before* this tm->month starts. - int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year - int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11 + static const int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year + int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11 // If this is a leap year, and we're past February, add a day: if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) { @@ -435,6 +435,7 @@ time_t gm_mktime(struct tm *tm) return result; #else - return mktime(tm); + struct tm tmCopy = *tm; + return mktime(&tmCopy); #endif } diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 06dd34c16..cf6db0239 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -54,7 +54,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false); RTCSetResult readFromRTC(); -time_t gm_mktime(struct tm *tm); +time_t gm_mktime(const struct tm *tm); #define SEC_PER_DAY 86400 #define SEC_PER_HOUR 3600 diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index c5a4106e7..6d29e9f7f 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -266,52 +266,8 @@ void menuHandler::FrequencySlotPicker() // Calculate number of channels (copied from RadioInterface::applyModemConfig()) meshtastic_Config_LoRaConfig &loraConfig = config.lora; - double bw = loraConfig.bandwidth; - if (loraConfig.use_preset) { - switch (loraConfig.modem_preset) { - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - break; - default: - bw = (myRegion->wideLora) ? 812.5 : 250; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: - bw = (myRegion->wideLora) ? 406.25 : 125; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: - bw = (myRegion->wideLora) ? 406.25 : 125; - break; - } - } else { - bw = loraConfig.bandwidth; - if (bw == 31) // This parameter is not an integer - bw = 31.25; - if (bw == 62) // Fix for 62.5Khz bandwidth - bw = 62.5; - if (bw == 200) - bw = 203.125; - if (bw == 400) - bw = 406.25; - if (bw == 800) - bw = 812.5; - if (bw == 1600) - bw = 1625.0; - } + double bw = loraConfig.use_preset ? modemPresetToBwKHz(loraConfig.modem_preset, myRegion->wideLora) + : bwCodeToKHz(loraConfig.bandwidth); uint32_t numChannels = 0; if (myRegion) { diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 45fd0bf5f..1b964678b 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -140,7 +140,7 @@ struct ScreenColor { uint8_t b; bool useVariant; - ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false) + explicit ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false) : r(rIn), g(gIn), b(bIn), useVariant(variantIn) { } diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index f2514eea1..bbb0ee00f 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -22,4 +22,99 @@ struct RegionInfo { extern const RegionInfo regions[]; extern const RegionInfo *myRegion; -extern void initRegion(); \ No newline at end of file +extern void initRegion(); + +static inline float bwCodeToKHz(uint16_t bwCode) +{ + if (bwCode == 31) + return 31.25f; + if (bwCode == 62) + return 62.5f; + if (bwCode == 200) + return 203.125f; + if (bwCode == 400) + return 406.25f; + if (bwCode == 800) + return 812.5f; + if (bwCode == 1600) + return 1625.0f; + return (float)bwCode; +} + +static inline uint16_t bwKHzToCode(float bwKHz) +{ + if (bwKHz > 31.24f && bwKHz < 31.26f) + return 31; + if (bwKHz > 62.49f && bwKHz < 62.51f) + return 62; + if (bwKHz > 203.12f && bwKHz < 203.13f) + return 200; + if (bwKHz > 406.24f && bwKHz < 406.26f) + return 400; + if (bwKHz > 812.49f && bwKHz < 812.51f) + return 800; + if (bwKHz > 1624.99f && bwKHz < 1625.01f) + return 1600; + return (uint16_t)(bwKHz + 0.5f); +} + +static inline void modemPresetToParams(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora, float &bwKHz, uint8_t &sf, + uint8_t &cr) +{ + switch (preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + bwKHz = wideLora ? 1625.0f : 500.0f; + cr = 5; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 7; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 8; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 9; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 10; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: + bwKHz = wideLora ? 1625.0f : 500.0f; + cr = 8; + sf = 11; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: + bwKHz = wideLora ? 406.25f : 125.0f; + cr = 8; + sf = 11; + break; + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: + bwKHz = wideLora ? 406.25f : 125.0f; + cr = 8; + sf = 12; + break; + default: // LONG_FAST (or illegal) + bwKHz = wideLora ? 812.5f : 250.0f; + cr = 5; + sf = 11; + break; + } +} + +static inline float modemPresetToBwKHz(meshtastic_Config_LoRaConfig_ModemPreset preset, bool wideLora) +{ + float bwKHz = 0; + uint8_t sf = 0; + uint8_t cr = 0; + modemPresetToParams(preset, wideLora, bwKHz, sf, cr); + return bwKHz; +} \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 375bc76e3..f87fa58b1 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -13,6 +13,7 @@ #include "PacketHistory.h" #include "PowerFSM.h" #include "RTC.h" +#include "RadioInterface.h" #include "Router.h" #include "SPILock.h" #include "SafeFile.h" @@ -1297,6 +1298,13 @@ void NodeDB::loadFromDisk() LOG_INFO("Loaded saved config version %d", config.version); } } + + // Coerce LoRa config fields derived from presets while bootstrapping. + // Some clients/UI components display bandwidth/spread_factor directly from config even in preset mode. + if (config.has_lora && config.lora.use_preset) { + RadioInterface::bootstrapLoRaConfigFromPreset(config.lora); + } + if (backupSecurity.private_key.size > 0) { LOG_DEBUG("Restoring backup of security config"); config.security = backupSecurity; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index aaaca719e..26ef162b9 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -220,6 +220,34 @@ void initRegion() myRegion = r; } +void RadioInterface::bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig) +{ + if (!loraConfig.use_preset) { + return; + } + + // Find region info to determine whether "wide" LoRa is permitted (2.4 GHz uses wider bandwidth codes). + const RegionInfo *r = regions; + for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != loraConfig.region; r++) + ; + + const bool regionWideLora = r->wideLora; + + float bwKHz = 0; + uint8_t sf = 0; + uint8_t cr = 0; + modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr); + + // If selected preset requests a bandwidth larger than the region span, fall back to LONG_FAST. + if (r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && (r->freqEnd - r->freqStart) < (bwKHz / 1000.0f)) { + loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; + modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr); + } + + loraConfig.bandwidth = bwKHzToCode(bwKHz); + loraConfig.spread_factor = sf; +} + /** * ## LoRaWAN for North America @@ -474,54 +502,7 @@ void RadioInterface::applyModemConfig() bool validConfig = false; // We need to check for a valid configuration while (!validConfig) { if (loraConfig.use_preset) { - - switch (loraConfig.modem_preset) { - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - cr = 5; - sf = 7; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 7; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 8; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 9; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 10; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO: - bw = (myRegion->wideLora) ? 1625.0 : 500; - cr = 8; - sf = 11; - break; - default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal. - bw = (myRegion->wideLora) ? 812.5 : 250; - cr = 5; - sf = 11; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: - bw = (myRegion->wideLora) ? 406.25 : 125; - cr = 8; - sf = 11; - break; - case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: - bw = (myRegion->wideLora) ? 406.25 : 125; - cr = 8; - sf = 12; - break; - } + modemPresetToParams(loraConfig.modem_preset, myRegion->wideLora, bw, sf, cr); if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) { cr = loraConfig.coding_rate; LOG_INFO("Using custom Coding Rate %u", cr); @@ -529,20 +510,7 @@ void RadioInterface::applyModemConfig() } else { sf = loraConfig.spread_factor; cr = loraConfig.coding_rate; - bw = loraConfig.bandwidth; - - if (bw == 31) // This parameter is not an integer - bw = 31.25; - if (bw == 62) // Fix for 62.5Khz bandwidth - bw = 62.5; - if (bw == 200) - bw = 203.125; - if (bw == 400) - bw = 406.25; - if (bw == 800) - bw = 812.5; - if (bw == 1600) - bw = 1625.0; + bw = bwCodeToKHz(loraConfig.bandwidth); } if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) { diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6049a11cc..e4dc02de5 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -7,6 +7,9 @@ #include "airtime.h" #include "error.h" +// Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file. +typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig; + #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission #define MAX_LORA_PAYLOAD_LEN 255 // max length of 255 per Semtech's datasheets on SX12xx @@ -115,6 +118,12 @@ class RadioInterface virtual ~RadioInterface() {} + /** + * Coerce LoRa config fields (bandwidth/spread_factor) derived from presets. + * This is used during early bootstrapping so UIs that display these fields directly remain consistent. + */ + static void bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig); + /** * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) * diff --git a/test/test_radio/test_main.cpp b/test/test_radio/test_main.cpp new file mode 100644 index 000000000..fbe2b1b13 --- /dev/null +++ b/test/test_radio/test_main.cpp @@ -0,0 +1,100 @@ +#include "MeshRadio.h" +#include "RadioInterface.h" +#include "TestUtil.h" +#include + +#include "meshtastic/config.pb.h" + +static void test_bwCodeToKHz_specialMappings() +{ + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 31.25f, bwCodeToKHz(31)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 62.5f, bwCodeToKHz(62)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 203.125f, bwCodeToKHz(200)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 406.25f, bwCodeToKHz(400)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 812.5f, bwCodeToKHz(800)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 1625.0f, bwCodeToKHz(1600)); +} + +static void test_bwCodeToKHz_passthrough() +{ + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 125.0f, bwCodeToKHz(125)); + TEST_ASSERT_FLOAT_WITHIN(0.0001f, 250.0f, bwCodeToKHz(250)); +} + +static void test_bootstrapLoRaConfigFromPreset_noopWhenUsePresetFalse() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = false; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + cfg.bandwidth = 123; + cfg.spread_factor = 8; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(123, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(8, cfg.spread_factor); + TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST, cfg.modem_preset); +} + +static void test_bootstrapLoRaConfigFromPreset_setsDerivedFields_nonWideRegion() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_US; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(250, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(9, cfg.spread_factor); +} + +static void test_bootstrapLoRaConfigFromPreset_setsDerivedFields_wideRegion() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL_UINT16(800, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(9, cfg.spread_factor); +} + +static void test_bootstrapLoRaConfigFromPreset_fallsBackIfBandwidthExceedsRegionSpan() +{ + meshtastic_Config_LoRaConfig cfg = meshtastic_Config_LoRaConfig_init_zero; + cfg.use_preset = true; + cfg.region = meshtastic_Config_LoRaConfig_RegionCode_EU_868; + cfg.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO; + + RadioInterface::bootstrapLoRaConfigFromPreset(cfg); + + TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, cfg.modem_preset); + TEST_ASSERT_EQUAL_UINT16(250, cfg.bandwidth); + TEST_ASSERT_EQUAL_UINT32(11, cfg.spread_factor); +} + +void setUp(void) {} +void tearDown(void) {} + +void setup() +{ + delay(10); + delay(2000); + + initializeTestEnvironment(); + + UNITY_BEGIN(); + RUN_TEST(test_bwCodeToKHz_specialMappings); + RUN_TEST(test_bwCodeToKHz_passthrough); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_noopWhenUsePresetFalse); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_setsDerivedFields_nonWideRegion); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_setsDerivedFields_wideRegion); + RUN_TEST(test_bootstrapLoRaConfigFromPreset_fallsBackIfBandwidthExceedsRegionSpan); + exit(UNITY_END()); +} + +void loop() {} From 8a9830282ab48ab52bbf68e46a4af52161535120 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 25 Jan 2026 20:54:17 -0600 Subject: [PATCH 018/223] Move Lora Init code into LoraInit.cpp/h (#9435) * Move Lora Init code into LoraInit.cpp/h * Add missed extern definition * More carefully check for nullptr in LoraInit.cpp * STM32 fixes * Add extern SPI1 for HW_SPI1_DEVICE * Move initLora to RadioInterface.h --- src/main.cpp | 279 +--------------------------------- src/mesh/RadioInterface.cpp | 294 ++++++++++++++++++++++++++++++++++++ src/mesh/RadioInterface.h | 2 + src/mesh/Router.cpp | 1 - 4 files changed, 302 insertions(+), 274 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ea114ea34..c063aaa6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -77,29 +77,10 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include "mqtt/MQTT.h" #endif -#include "LLCC68Interface.h" -#include "LR1110Interface.h" -#include "LR1120Interface.h" -#include "LR1121Interface.h" -#include "RF95Interface.h" -#include "SX1262Interface.h" -#include "SX1268Interface.h" -#include "SX1280Interface.h" -#include "detect/LoRaRadioType.h" - -#ifdef ARCH_STM32WL -#include "STM32WLE5JCInterface.h" -#endif - -#if defined(ARCH_PORTDUINO) -#include "platform/portduino/SimRadio.h" -#endif - #ifdef ARCH_PORTDUINO #include "linux/LinuxHardwareI2C.h" #include "mesh/raspihttp/PiWebServer.h" #include "platform/portduino/PortduinoGlue.h" -#include "platform/portduino/USBHal.h" #include #include #include @@ -254,9 +235,6 @@ ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE; Adafruit_DRV2605 drv; #endif -// Global LoRa radio type -LoRaRadioType radioType = NO_RADIO; - bool isVibrating = false; bool eink_found = true; @@ -293,6 +271,7 @@ const char *getDeviceName() return name; } +// TODO remove from main.cpp static int32_t ledBlinker() { // Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if @@ -388,6 +367,7 @@ void setup() // boot sequence will follow when battery level raises to safe mode waitUntilPowerLevelSafe(); + // TODO remove all device-specific setup code to variant.cpp #if defined(R1_NEO) pinMode(DCDC_EN_HOLD, OUTPUT); digitalWrite(DCDC_EN_HOLD, HIGH); @@ -489,11 +469,6 @@ void setup() #endif concurrency::hasBeenSetup = true; -#if ARCH_PORTDUINO - SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); -#else - SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); -#endif meshtastic_Config_DisplayConfig_OledType screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; @@ -632,6 +607,7 @@ void setup() OSThread::setup(); + // TODO make this ifdef based on defined pins and move from main.cpp #if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) // The ThinkNodes have their own blink logic // ledPeriodic = new Periodic("Blink", elecrowLedBlinker); @@ -1009,6 +985,7 @@ void setup() } #endif // HAS_SCREEN + // TODO Remove magic string // setup TZ prior to time actions. #if !MESHTASTIC_EXCLUDE_TZ LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string @@ -1304,252 +1281,7 @@ void setup() #endif #endif -#ifdef ARCH_PORTDUINO - // as one can't use a function pointer to the class constructor: - auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, - RADIOLIB_PIN_TYPE busy) { - switch (portduino_config.lora_module) { - case use_rf95: - return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); - case use_sx1262: - return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); - case use_sx1268: - return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); - case use_sx1280: - return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); - case use_lr1110: - return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); - case use_lr1120: - return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); - case use_lr1121: - return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); - case use_llcc68: - return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); - case use_simradio: - return (RadioInterface *)new SimRadio; - default: - assert(0); // shouldn't happen - return (RadioInterface *)nullptr; - } - }; - - LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(), - portduino_config.lora_spi_dev.c_str()); - if (portduino_config.lora_spi_dev == "ch341") { - RadioLibHAL = ch341Hal; - } else { - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - } - rIf = - loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin, - portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin); - - if (!rIf->init()) { - LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str()); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str()); - } - -#elif defined(HW_SPI1_DEVICE) - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); -#else // HW_SPI1_DEVICE - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); -#endif - - // radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) -#if defined(USE_STM32WLx) - if (!rIf) { - rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No STM32WL radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("STM32WL init success"); - radioType = STM32WLx_RADIO; - } - } -#endif - -#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); - if (!rIf->init()) { - LOG_WARN("No RF95 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("RF95 init success"); - radioType = RF95_RADIO; - } - } -#endif - -#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); -#ifdef SX126X_DIO3_TCXO_VOLTAGE - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); -#endif - if (!sxIf->init()) { - LOG_WARN("No SX1262 radio"); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success"); - rIf = sxIf; - radioType = SX1262_RADIO; - } - } -#endif - -#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // try using the specified TCXO voltage - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); - if (!sxIf->init()) { - LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; - radioType = SX1262_RADIO; - } - } - - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead - rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1262 init success, XTAL, Vref 0.0V"); - radioType = SX1262_RADIO; - } - } -#endif - -#if defined(USE_SX1268) -#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - // try using the specified TCXO voltage - auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); - if (!sxIf->init()) { - LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; - } else { - LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; - radioType = SX1268_RADIO; - } - } -#endif - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1268 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1268 init success"); - radioType = SX1268_RADIO; - } - } -#endif - -#if defined(USE_LLCC68) - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); - if (!rIf->init()) { - LOG_WARN("No LLCC68 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LLCC68 init success"); - radioType = LLCC68_RADIO; - } - } -#endif - -#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1110 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1110 init success"); - radioType = LR1110_RADIO; - } - } -#endif - -#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if (!rIf) { - rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1120 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1120 init success"); - radioType = LR1120_RADIO; - } - } -#endif - -#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 - if (!rIf) { - rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); - if (!rIf->init()) { - LOG_WARN("No LR1121 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("LR1121 init success"); - radioType = LR1121_RADIO; - } - } -#endif - -#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 - if (!rIf) { - rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); - if (!rIf->init()) { - LOG_WARN("No SX1280 radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("SX1280 init success"); - radioType = SX1280_RADIO; - } - } -#endif - - // check if the radio chip matches the selected region - if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) { - LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset"); - config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; - nodeDB->saveToDisk(SEGMENT_CONFIG); - - if (!rIf->reconfigure()) { - LOG_WARN("Reconfigure failed, rebooting"); - if (screen) { - screen->showSimpleBanner("Rebooting..."); - } - rebootAtMsec = millis() + 5000; - } - } + initLoRa(); lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation) @@ -1638,6 +1370,7 @@ bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for // This will suppress the current delay and instead try to run ASAP. bool runASAP; +// TODO find better home than main.cpp extern meshtastic_DeviceMetadata getDeviceMetadata() { meshtastic_DeviceMetadata deviceMetadata; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index d0097a46a..b489f7939 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -1,17 +1,36 @@ #include "RadioInterface.h" #include "Channels.h" #include "DisplayFormatters.h" +#include "LLCC68Interface.h" +#include "LR1110Interface.h" +#include "LR1120Interface.h" +#include "LR1121Interface.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" +#include "RF95Interface.h" #include "Router.h" +#include "SX1262Interface.h" +#include "SX1268Interface.h" +#include "SX1280Interface.h" #include "configuration.h" +#include "detect/LoRaRadioType.h" #include "main.h" #include "sleep.h" #include #include #include +#ifdef ARCH_PORTDUINO +#include "platform/portduino/PortduinoGlue.h" +#include "platform/portduino/SimRadio.h" +#include "platform/portduino/USBHal.h" +#endif + +#ifdef ARCH_STM32WL> +#include "STM32WLE5JCInterface.h" +#endif + // Calculate 2^n without calling pow() uint32_t pow_of_2(uint32_t n) { @@ -205,6 +224,281 @@ bool RadioInterface::uses_default_frequency_slot = true; static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; +// Global LoRa radio type +LoRaRadioType radioType = NO_RADIO; + +extern RadioInterface *rIf; +extern RadioLibHal *RadioLibHAL; +#if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32) +extern SPIClass SPI1; +#endif + +bool initLoRa() +{ + if (rIf != nullptr) { + delete rIf; + rIf = nullptr; + } + +#if ARCH_PORTDUINO + SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); +#else + SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); +#endif + +#ifdef ARCH_PORTDUINO + // as one can't use a function pointer to the class constructor: + auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy) { + switch (portduino_config.lora_module) { + case use_rf95: + return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); + case use_sx1262: + return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); + case use_sx1268: + return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); + case use_sx1280: + return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); + case use_lr1110: + return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); + case use_lr1120: + return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); + case use_lr1121: + return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); + case use_llcc68: + return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); + case use_simradio: + return (RadioInterface *)new SimRadio; + default: + assert(0); // shouldn't happen + return (RadioInterface *)nullptr; + } + }; + + LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(), + portduino_config.lora_spi_dev.c_str()); + if (portduino_config.lora_spi_dev == "ch341") { + RadioLibHAL = ch341Hal; + } else { + if (RadioLibHAL != nullptr) { + delete RadioLibHAL; + RadioLibHAL = nullptr; + } + RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + } + rIf = + loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin, + portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin); + + if (!rIf->init()) { + LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str()); + delete rIf; + rIf = NULL; + exit(EXIT_FAILURE); + } else { + LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str()); + } + +#elif defined(HW_SPI1_DEVICE) + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); +#else // HW_SPI1_DEVICE + LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); +#endif + +// radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) +#if defined(USE_STM32WLx) + if (!rIf) { + rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No STM32WL radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("STM32WL init success"); + radioType = STM32WLx_RADIO; + } + } +#endif + +#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); + if (!rIf->init()) { + LOG_WARN("No RF95 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("RF95 init success"); + radioType = RF95_RADIO; + } + } +#endif + +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); +#ifdef SX126X_DIO3_TCXO_VOLTAGE + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); +#endif + if (!sxIf->init()) { + LOG_WARN("No SX1262 radio"); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success"); + rIf = sxIf; + radioType = SX1262_RADIO; + } + } +#endif + +#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // try using the specified TCXO voltage + auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); + if (!sxIf->init()) { + LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + rIf = sxIf; + radioType = SX1262_RADIO; + } + } + + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead + rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1262 init success, XTAL, Vref 0.0V"); + radioType = SX1262_RADIO; + } + } +#endif + +#if defined(USE_SX1268) +#if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + // try using the specified TCXO voltage + auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); + if (!sxIf->init()) { + LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + delete sxIf; + rIf = NULL; + } else { + LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); + rIf = sxIf; + radioType = SX1268_RADIO; + } + } +#endif + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1268 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1268 init success"); + radioType = SX1268_RADIO; + } + } +#endif + +#if defined(USE_LLCC68) + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + if (!rIf->init()) { + LOG_WARN("No LLCC68 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LLCC68 init success"); + radioType = LLCC68_RADIO; + } + } +#endif + +#if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { + rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1110 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1110 init success"); + radioType = LR1110_RADIO; + } + } +#endif + +#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if (!rIf) { + rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1120 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1120 init success"); + radioType = LR1120_RADIO; + } + } +#endif + +#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 + if (!rIf) { + rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); + if (!rIf->init()) { + LOG_WARN("No LR1121 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("LR1121 init success"); + radioType = LR1121_RADIO; + } + } +#endif + +#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 + if (!rIf) { + rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); + if (!rIf->init()) { + LOG_WARN("No SX1280 radio"); + delete rIf; + rIf = NULL; + } else { + LOG_INFO("SX1280 init success"); + radioType = SX1280_RADIO; + } + } +#endif + + // check if the radio chip matches the selected region + if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) { + LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset"); + config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET; + nodeDB->saveToDisk(SEGMENT_CONFIG); + + if (rIf && !rIf->reconfigure()) { + LOG_WARN("Reconfigure failed, rebooting"); + if (screen) { + screen->showSimpleBanner("Rebooting..."); + } + rebootAtMsec = millis() + 5000; + } + } + return rIf != nullptr; +} + void initRegion() { const RegionInfo *r = regions; diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6049a11cc..ddacf2c5d 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -270,5 +270,7 @@ class RadioInterface } }; +bool initLoRa(); + /// Debug printing for packets void printPacket(const char *prefix, const meshtastic_MeshPacket *p); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a3861521a..1a3270040 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -7,7 +7,6 @@ #include "RTC.h" #include "configuration.h" -#include "detect/LoRaRadioType.h" #include "main.h" #include "mesh-pb-constants.h" #include "meshUtils.h" From c038cfe69a17d5d4ab9aa22a5437a378b01063e1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Jan 2026 11:54:14 -0600 Subject: [PATCH 019/223] Move device code from main.cpp to earlyInitVariant (#9438) --- src/input/TrackballInterruptBase.cpp | 6 +- src/main.cpp | 80 ++----------------- src/main.h | 12 +-- src/platform/extra_variants/README.md | 2 +- src/platform/nrf52/main-nrf52.cpp | 60 +------------- .../ELECROW-ThinkNode-M5/platformio.ini | 4 + .../esp32s3/ELECROW-ThinkNode-M5/variant.cpp | 12 +++ .../esp32s3/ELECROW-ThinkNode-M5/variant.h | 2 + .../hackaday-communicator/platformio.ini | 6 +- .../esp32s3/hackaday-communicator/variant.cpp | 6 ++ variants/esp32s3/t-deck-pro/platformio.ini | 4 + variants/esp32s3/t-deck-pro/variant.cpp | 14 ++++ variants/esp32s3/t-deck/platformio.ini | 4 + variants/esp32s3/t-deck/variant.cpp | 23 ++++++ variants/esp32s3/tlora-pager/platformio.ini | 4 + variants/esp32s3/tlora-pager/variant.cpp | 31 +++++++ .../nrf52840/ELECROW-ThinkNode-M1/variant.cpp | 18 +++++ .../heltec_mesh_node_t114-inkhud/variant.cpp | 8 ++ .../heltec_mesh_node_t114/variant.cpp | 8 ++ .../nrf52840/heltec_mesh_node_t114/variant.h | 2 +- .../nrf52840/heltec_mesh_solar/variant.cpp | 8 ++ variants/nrf52840/meshlink/variant.cpp | 7 ++ variants/nrf52840/r1-neo/variant.cpp | 8 ++ variants/nrf52840/rak_wismeshtap/variant.cpp | 17 ++++ variants/nrf52840/t-echo/variant.cpp | 10 +++ 25 files changed, 208 insertions(+), 148 deletions(-) create mode 100644 variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp create mode 100644 variants/esp32s3/hackaday-communicator/variant.cpp create mode 100644 variants/esp32s3/t-deck-pro/variant.cpp create mode 100644 variants/esp32s3/t-deck/variant.cpp create mode 100644 variants/esp32s3/tlora-pager/variant.cpp diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp index bbd07e199..bce62caaf 100644 --- a/src/input/TrackballInterruptBase.cpp +++ b/src/input/TrackballInterruptBase.cpp @@ -61,11 +61,7 @@ int32_t TrackballInterruptBase::runOnce() uint32_t pressDuration = millis() - pressStartTime; bool buttonStillPressed = false; -#if defined(T_DECK) - buttonStillPressed = (this->action == TB_ACTION_PRESSED); -#else buttonStillPressed = !digitalRead(_pinPress); -#endif if (!buttonStillPressed) { // Button released @@ -135,7 +131,7 @@ int32_t TrackballInterruptBase::runOnce() } #if defined(T_DECK) // T-deck gets a super-simple debounce on trackball - if (this->action == TB_ACTION_PRESSED && !pressDetected) { + if (this->action == TB_ACTION_PRESSED && (!pressDetected || pressStartTime == 0)) { // Start long press detection pressDetected = true; pressStartTime = millis(); diff --git a/src/main.cpp b/src/main.cpp index c063aaa6a..93cc89296 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,10 +43,6 @@ #include "MessageStore.h" #endif -#ifdef ELECROW_ThinkNode_M5 -PCA9557 io(0x18, &Wire); -#endif - #ifdef ARCH_ESP32 #include "freertosinc.h" #if !MESHTASTIC_EXCLUDE_WEBSERVER @@ -312,6 +308,9 @@ __attribute__((weak, noinline)) bool loopCanSleep() void lateInitVariant() __attribute__((weak)); void lateInitVariant() {} +void earlyInitVariant() __attribute__((weak)); +void earlyInitVariant() {} + // NRF52 (and probably other platforms) can report when system is in power failure mode // (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc). // For example NRF52 will prevent any flash writes in that case automatically @@ -367,27 +366,14 @@ void setup() // boot sequence will follow when battery level raises to safe mode waitUntilPowerLevelSafe(); - // TODO remove all device-specific setup code to variant.cpp -#if defined(R1_NEO) - pinMode(DCDC_EN_HOLD, OUTPUT); - digitalWrite(DCDC_EN_HOLD, HIGH); - pinMode(NRF_ON, OUTPUT); - digitalWrite(NRF_ON, HIGH); -#endif + // Defined in variant.cpp for early init code + earlyInitVariant(); #if defined(PIN_POWER_EN) pinMode(PIN_POWER_EN, OUTPUT); digitalWrite(PIN_POWER_EN, HIGH); #endif -#if defined(ELECROW_ThinkNode_M5) - Wire.begin(48, 47); - io.pinMode(PCA_PIN_EINK_EN, OUTPUT); - io.pinMode(PCA_PIN_POWER_EN, OUTPUT); - io.digitalWrite(PCA_PIN_POWER_EN, HIGH); - // io.pinMode(C2_PIN, OUTPUT); -#endif - #ifdef LED_POWER pinMode(LED_POWER, OUTPUT); digitalWrite(LED_POWER, LED_STATE_ON); @@ -412,62 +398,6 @@ void setup() #endif #endif -#if defined(T_DECK) - // GPIO10 manages all peripheral power supplies - // Turn on peripheral power immediately after MUC starts. - // If some boards are turned on late, ESP32 will reset due to low voltage. - // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) , - // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder) - pinMode(KB_POWERON, OUTPUT); - digitalWrite(KB_POWERON, HIGH); - // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus - // We need to initialize all CS pins in advance otherwise there will be SPI communication issues - // e.g. when detecting the SD card - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(TFT_CS, OUTPUT); - digitalWrite(TFT_CS, HIGH); - delay(100); -#elif defined(T_DECK_PRO) - pinMode(LORA_EN, OUTPUT); - digitalWrite(LORA_EN, HIGH); - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(PIN_EINK_CS, OUTPUT); - digitalWrite(PIN_EINK_CS, HIGH); -#elif defined(T_LORA_PAGER) - pinMode(LORA_CS, OUTPUT); - digitalWrite(LORA_CS, HIGH); - pinMode(SDCARD_CS, OUTPUT); - digitalWrite(SDCARD_CS, HIGH); - pinMode(TFT_CS, OUTPUT); - digitalWrite(TFT_CS, HIGH); - pinMode(KB_INT, INPUT_PULLUP); - // io expander - io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); - io.pinMode(EXPANDS_DRV_EN, OUTPUT); - io.digitalWrite(EXPANDS_DRV_EN, HIGH); - io.pinMode(EXPANDS_AMP_EN, OUTPUT); - io.digitalWrite(EXPANDS_AMP_EN, LOW); - io.pinMode(EXPANDS_LORA_EN, OUTPUT); - io.digitalWrite(EXPANDS_LORA_EN, HIGH); - io.pinMode(EXPANDS_GPS_EN, OUTPUT); - io.digitalWrite(EXPANDS_GPS_EN, HIGH); - io.pinMode(EXPANDS_KB_EN, OUTPUT); - io.digitalWrite(EXPANDS_KB_EN, HIGH); - io.pinMode(EXPANDS_SD_EN, OUTPUT); - io.digitalWrite(EXPANDS_SD_EN, HIGH); - io.pinMode(EXPANDS_GPIO_EN, OUTPUT); - io.digitalWrite(EXPANDS_GPIO_EN, HIGH); - io.pinMode(EXPANDS_SD_PULLEN, INPUT); -#elif defined(HACKADAY_COMMUNICATOR) - pinMode(KB_INT, INPUT); -#endif - concurrency::hasBeenSetup = true; meshtastic_Config_DisplayConfig_OledType screen_model = diff --git a/src/main.h b/src/main.h index c3528a63d..91e27951f 100644 --- a/src/main.h +++ b/src/main.h @@ -26,8 +26,8 @@ extern NRF52Bluetooth *nrf52Bluetooth; #if ARCH_PORTDUINO extern HardwareSPI *DisplaySPI; extern HardwareSPI *LoraSPI; - #endif + extern ScanI2C::DeviceAddress screen_found; extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; @@ -47,16 +47,16 @@ extern bool isUSBPowered; extern Adafruit_DRV2605 drv; #endif +#ifdef HAS_PCA9557 +#include +extern PCA9557 io; +#endif + #ifdef HAS_I2S #include "AudioThread.h" extern AudioThread *audioThread; #endif -#ifdef ELECROW_ThinkNode_M5 -#include -extern PCA9557 io; -#endif - #ifdef HAS_UDP_MULTICAST #include "mesh/udp/UdpMulticastHandler.h" extern UdpMulticastHandler *udpHandler; diff --git a/src/platform/extra_variants/README.md b/src/platform/extra_variants/README.md index e558502f0..838014c4f 100644 --- a/src/platform/extra_variants/README.md +++ b/src/platform/extra_variants/README.md @@ -5,7 +5,7 @@ This directory tree is designed to solve two problems. - The ESP32 arduino/platformio project doesn't support the nice "if initVariant() is found, call that after init" behavior of the nrf52 builds (they use initVariant() internally). - Over the years a lot of 'board specific' init code has been added to init() in main.cpp. It would be great to have a general/clean mechanism to allow developers to specify board specific/unique code in a clean fashion without mucking in main. -So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define lateInitVariant() if your board needs it. +So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define earlyInitVariant() and lateInitVariant() if your board needs them. earlyInitVariant() runs at the beginning of setup() directly after waitUntilPowerLevelSafe(); while lateInitVariant() runs after the LoRa radio is initialized. If you'd like a board specific variant to be run, add the variant.cpp file to an appropriately named subdirectory and check for \_VARIANT_boardname in the cpp file (so that your code is only built for your board). diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 2068fe2a7..11b05165c 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -430,15 +430,6 @@ void cpuDeepSleep(uint32_t msecToWake) Serial1.end(); #endif -#ifdef TTGO_T_ECHO - // To power off the T-Echo, the display must be set - // as an input pin; otherwise, there will be leakage current. - pinMode(PIN_EINK_CS, INPUT); - pinMode(PIN_EINK_DC, INPUT); - pinMode(PIN_EINK_RES, INPUT); - pinMode(PIN_EINK_BUSY, INPUT); -#endif - setBluetoothEnable(false); #ifdef RAK4630 @@ -449,57 +440,8 @@ void cpuDeepSleep(uint32_t msecToWake) // RAK-12039 set pin for Air quality sensor digitalWrite(AQ_SET_PIN, LOW); #endif -#ifdef RAK14014 - // GPIO restores input status, otherwise there will be leakage current - nrf_gpio_cfg_default(TFT_BL); - nrf_gpio_cfg_default(TFT_DC); - nrf_gpio_cfg_default(TFT_CS); - nrf_gpio_cfg_default(TFT_SCLK); - nrf_gpio_cfg_default(TFT_MOSI); - nrf_gpio_cfg_default(TFT_MISO); - nrf_gpio_cfg_default(SCREEN_TOUCH_INT); - nrf_gpio_cfg_default(WB_I2C1_SCL); - nrf_gpio_cfg_default(WB_I2C1_SDA); - - // nrf_gpio_cfg_default(WB_I2C2_SCL); - // nrf_gpio_cfg_default(WB_I2C2_SDA); -#endif -#endif -#ifdef MESHLINK -#ifdef PIN_WD_EN - digitalWrite(PIN_WD_EN, LOW); -#endif -#endif - -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_MESH_SOLAR) - nrf_gpio_cfg_default(PIN_GPS_PPS); - detachInterrupt(PIN_GPS_PPS); - detachInterrupt(PIN_BUTTON1); -#endif - -#ifdef ELECROW_ThinkNode_M1 - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - pinMode(pin, OUTPUT); - } - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - digitalWrite(pin, LOW); - } - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - NRF_GPIO->DIRCLR = (1 << pin); - } #endif + // Run shutdown code if specified in variant.cpp variant_shutdown(); // Sleepy trackers or sensors can low power "sleep" diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini index 9994cf665..ee51018d4 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini @@ -11,6 +11,10 @@ custom_meshtastic_requires_dfu = false extends = esp32s3_base board = ESP32-S3-WROOM-1-N4 +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/ELECROW-ThinkNode-M5> + build_flags = ${esp32s3_base.build_flags} -D ELECROW_ThinkNode_M5 diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp new file mode 100644 index 000000000..4b485a1a3 --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp @@ -0,0 +1,12 @@ +#include "variant.h" +#include + +PCA9557 io(0x18, &Wire); + +void earlyInitVariant() +{ + Wire.begin(48, 47); + io.pinMode(PCA_PIN_EINK_EN, OUTPUT); + io.pinMode(PCA_PIN_POWER_EN, OUTPUT); + io.digitalWrite(PCA_PIN_POWER_EN, HIGH); +} diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 5f5133e61..77a64f717 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -4,6 +4,8 @@ #define UART_TX 43 #define UART_RX 44 +#define HAS_PCA9557 + // LED // Both of these are on the GPIO expander #define PCA_LED_USER 1 // the Blue LED diff --git a/variants/esp32s3/hackaday-communicator/platformio.ini b/variants/esp32s3/hackaday-communicator/platformio.ini index 29b2c2305..8fd275c0e 100644 --- a/variants/esp32s3/hackaday-communicator/platformio.ini +++ b/variants/esp32s3/hackaday-communicator/platformio.ini @@ -6,6 +6,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/hackaday-communicator> + build_flags = ${esp32s3_base.build_flags} -D HACKADAY_COMMUNICATOR -D BOARD_HAS_PSRAM @@ -13,4 +17,4 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-Arduino_GFX packageName=https://github.com/meshtastic/Arduino_GFX gitBranch=master - https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip + https://github.com/meshtastic/Arduino_GFX/archive/054e81ffaf23784830a734e3c184346789349406.zip \ No newline at end of file diff --git a/variants/esp32s3/hackaday-communicator/variant.cpp b/variants/esp32s3/hackaday-communicator/variant.cpp new file mode 100644 index 000000000..d85b2abb5 --- /dev/null +++ b/variants/esp32s3/hackaday-communicator/variant.cpp @@ -0,0 +1,6 @@ +#include "variant.h" +#include "Arduino.h" +void earlyInitVariant() +{ + pinMode(KB_INT, INPUT); +} \ No newline at end of file diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini index aca31b599..5ba82d045 100644 --- a/variants/esp32s3/t-deck-pro/platformio.ini +++ b/variants/esp32s3/t-deck-pro/platformio.ini @@ -15,6 +15,10 @@ board = t-deck-pro board_check = true upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/t-deck-pro> + build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/t-deck-pro -D T_DECK_PRO diff --git a/variants/esp32s3/t-deck-pro/variant.cpp b/variants/esp32s3/t-deck-pro/variant.cpp new file mode 100644 index 000000000..509726c52 --- /dev/null +++ b/variants/esp32s3/t-deck-pro/variant.cpp @@ -0,0 +1,14 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(LORA_EN, OUTPUT); + digitalWrite(LORA_EN, HIGH); + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(PIN_EINK_CS, OUTPUT); + digitalWrite(PIN_EINK_CS, HIGH); +} \ No newline at end of file diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini index 58335796a..54ffe43fe 100644 --- a/variants/esp32s3/t-deck/platformio.ini +++ b/variants/esp32s3/t-deck/platformio.ini @@ -17,6 +17,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/t-deck> + build_flags = ${esp32s3_base.build_flags} -D T_DECK -D BOARD_HAS_PSRAM diff --git a/variants/esp32s3/t-deck/variant.cpp b/variants/esp32s3/t-deck/variant.cpp new file mode 100644 index 000000000..6b68f142c --- /dev/null +++ b/variants/esp32s3/t-deck/variant.cpp @@ -0,0 +1,23 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + // GPIO10 manages all peripheral power supplies + // Turn on peripheral power immediately after MUC starts. + // If some boards are turned on late, ESP32 will reset due to low voltage. + // ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) , + // TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder) + pinMode(KB_POWERON, OUTPUT); + digitalWrite(KB_POWERON, HIGH); + // T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus + // We need to initialize all CS pins in advance otherwise there will be SPI communication issues + // e.g. when detecting the SD card + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + delay(100); +} \ No newline at end of file diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index ea6369f29..7b4fc5312 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -17,6 +17,10 @@ board_check = true board_build.partitions = default_16MB.csv upload_protocol = esptool +build_src_filter = + ${esp32s3_base.build_src_filter} + +<../variants/esp32s3/tlora-pager> + build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/tlora-pager -D T_LORA_PAGER diff --git a/variants/esp32s3/tlora-pager/variant.cpp b/variants/esp32s3/tlora-pager/variant.cpp new file mode 100644 index 000000000..7b0cbdfec --- /dev/null +++ b/variants/esp32s3/tlora-pager/variant.cpp @@ -0,0 +1,31 @@ +#include "variant.h" +#include "ExtensionIOXL9555.hpp" +extern ExtensionIOXL9555 io; + +void earlyInitVariant() +{ + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + pinMode(KB_INT, INPUT_PULLUP); + // io expander + io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); + io.pinMode(EXPANDS_DRV_EN, OUTPUT); + io.digitalWrite(EXPANDS_DRV_EN, HIGH); + io.pinMode(EXPANDS_AMP_EN, OUTPUT); + io.digitalWrite(EXPANDS_AMP_EN, LOW); + io.pinMode(EXPANDS_LORA_EN, OUTPUT); + io.digitalWrite(EXPANDS_LORA_EN, HIGH); + io.pinMode(EXPANDS_GPS_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPS_EN, HIGH); + io.pinMode(EXPANDS_KB_EN, OUTPUT); + io.digitalWrite(EXPANDS_KB_EN, HIGH); + io.pinMode(EXPANDS_SD_EN, OUTPUT); + io.digitalWrite(EXPANDS_SD_EN, HIGH); + io.pinMode(EXPANDS_GPIO_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPIO_EN, HIGH); + io.pinMode(EXPANDS_SD_PULLEN, INPUT); +} \ No newline at end of file diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp index cae079b74..2ce84bc57 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp @@ -42,3 +42,21 @@ void initVariant() pinMode(PIN_LED3, OUTPUT); ledOff(PIN_LED3); } + +void variant_shutdown() +{ + for (int pin = 0; pin < 48; pin++) { + if (pin == SX126X_BUSY || pin == PIN_SPI_SCK || pin == SX126X_DIO1 || pin == PIN_SPI_MOSI || pin == PIN_SPI_MISO || + pin == SX126X_CS || pin == SX126X_RESET || pin == PIN_NFC1 || pin == PIN_NFC2 || pin == PIN_BUTTON1 || + pin == PIN_BUTTON2) { + continue; + } + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + if (pin >= 32) { + NRF_P1->DIRCLR = (1 << (pin - 32)); + } else { + NRF_GPIO->DIRCLR = (1 << pin); + } + } +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp index 85c9f4a72..b8b0e21c5 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -36,3 +37,10 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.cpp b/variants/nrf52840/heltec_mesh_node_t114/variant.cpp index 85c9f4a72..b8b0e21c5 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.cpp +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -36,3 +37,10 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index fb7f61ac7..cf14b5e04 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -156,7 +156,7 @@ No longer populated on PCB // The bluetooth transmit power on the nRF52840 is adjustable from -20dB to +8dB in steps of 4dB // so NRF52_BLE_TX_POWER can be set to -20, -16, -12, -8, -4, 0 (default), 4, and 8. -//#define NRF52_BLE_TX_POWER 8 +// #define NRF52_BLE_TX_POWER 8 /* * GPS pins diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp index c13f006d7..3b2612da8 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.cpp +++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp @@ -19,6 +19,7 @@ */ #include "variant.h" +#include "Arduino.h" #include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" @@ -38,3 +39,10 @@ void initVariant() digitalWrite(PIN_SCREEN_VDD_CTL, LOW); // Start with power on #endif } + +void variant_shutdown() +{ + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); +} \ No newline at end of file diff --git a/variants/nrf52840/meshlink/variant.cpp b/variants/nrf52840/meshlink/variant.cpp index 81a5097c4..f35bbd1af 100644 --- a/variants/nrf52840/meshlink/variant.cpp +++ b/variants/nrf52840/meshlink/variant.cpp @@ -20,4 +20,11 @@ void initVariant() pinMode(PIN_WD_EN, OUTPUT); digitalWrite(PIN_WD_EN, HIGH); // Enable the Watchdog at boot #endif +} + +void variant_shutdown() +{ +#ifdef PIN_WD_EN + digitalWrite(PIN_WD_EN, LOW); +#endif } \ No newline at end of file diff --git a/variants/nrf52840/r1-neo/variant.cpp b/variants/nrf52840/r1-neo/variant.cpp index f87c041aa..d87b88c85 100644 --- a/variants/nrf52840/r1-neo/variant.cpp +++ b/variants/nrf52840/r1-neo/variant.cpp @@ -43,3 +43,11 @@ void initVariant() // pinMode(PIN_3V3_EN, OUTPUT); // digitalWrite(PIN_3V3_EN, HIGH); } + +void earlyInitVariant() +{ + pinMode(DCDC_EN_HOLD, OUTPUT); + digitalWrite(DCDC_EN_HOLD, HIGH); + pinMode(NRF_ON, OUTPUT); + digitalWrite(NRF_ON, HIGH); +} diff --git a/variants/nrf52840/rak_wismeshtap/variant.cpp b/variants/nrf52840/rak_wismeshtap/variant.cpp index 5a3587982..36572b074 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.cpp +++ b/variants/nrf52840/rak_wismeshtap/variant.cpp @@ -42,4 +42,21 @@ void initVariant() // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); +} + +void variant_shutdown() +{ + // GPIO restores input status, otherwise there will be leakage current + nrf_gpio_cfg_default(TFT_BL); + nrf_gpio_cfg_default(TFT_DC); + nrf_gpio_cfg_default(TFT_CS); + nrf_gpio_cfg_default(TFT_SCLK); + nrf_gpio_cfg_default(TFT_MOSI); + nrf_gpio_cfg_default(TFT_MISO); + nrf_gpio_cfg_default(SCREEN_TOUCH_INT); + nrf_gpio_cfg_default(WB_I2C1_SCL); + nrf_gpio_cfg_default(WB_I2C1_SDA); + + // nrf_gpio_cfg_default(WB_I2C2_SCL); + // nrf_gpio_cfg_default(WB_I2C2_SDA); } \ No newline at end of file diff --git a/variants/nrf52840/t-echo/variant.cpp b/variants/nrf52840/t-echo/variant.cpp index cae079b74..cb64530f6 100644 --- a/variants/nrf52840/t-echo/variant.cpp +++ b/variants/nrf52840/t-echo/variant.cpp @@ -42,3 +42,13 @@ void initVariant() pinMode(PIN_LED3, OUTPUT); ledOff(PIN_LED3); } + +void variant_shutdown() +{ + // To power off the T-Echo, the display must be set + // as an input pin; otherwise, there will be leakage current. + pinMode(PIN_EINK_CS, INPUT); + pinMode(PIN_EINK_DC, INPUT); + pinMode(PIN_EINK_RES, INPUT); + pinMode(PIN_EINK_BUSY, INPUT); +} \ No newline at end of file From 3d58c6e91676f36ee518a3f99d6b825a146091e4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Jan 2026 14:28:05 -0600 Subject: [PATCH 020/223] Trackball revamp (#9440) * Trackball revamp * Use Throttle * Volatile! --- src/input/TrackballInterruptBase.cpp | 82 ++++++++++++++++++++++----- src/input/TrackballInterruptBase.h | 14 ++++- src/input/TrackballInterruptImpl1.cpp | 35 ++++-------- variants/esp32s3/t-deck/variant.h | 1 + 4 files changed, 91 insertions(+), 41 deletions(-) diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp index bce62caaf..1bbe75629 100644 --- a/src/input/TrackballInterruptBase.cpp +++ b/src/input/TrackballInterruptBase.cpp @@ -1,5 +1,7 @@ #include "TrackballInterruptBase.h" +#include "Throttle.h" #include "configuration.h" + extern bool osk_found; TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {} @@ -55,6 +57,20 @@ int32_t TrackballInterruptBase::runOnce() { InputEvent e = {}; e.inputEvent = INPUT_BROKER_NONE; +#if TB_THRESHOLD + if (lastInterruptTime && !Throttle::isWithinTimespanMs(lastInterruptTime, 1000)) { + left_counter = 0; + right_counter = 0; + up_counter = 0; + down_counter = 0; + lastInterruptTime = 0; + } +#ifdef INPUT_DEBUG + if (left_counter > 0 || right_counter > 0 || up_counter > 0 || down_counter > 0) { + LOG_DEBUG("L %u R %u U %u D %u, time %u", left_counter, right_counter, up_counter, down_counter, millis()); + } +#endif +#endif // Handle long press detection for press button if (pressDetected && pressStartTime > 0) { @@ -130,23 +146,31 @@ int32_t TrackballInterruptBase::runOnce() } } -#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball +#if TB_THRESHOLD if (this->action == TB_ACTION_PRESSED && (!pressDetected || pressStartTime == 0)) { // Start long press detection pressDetected = true; pressStartTime = millis(); // Don't send event yet, wait to see if it's a long press - } else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) { - // LOG_DEBUG("Trackball event UP"); + } else if (up_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event UP %u", millis()); +#endif e.inputEvent = this->_eventUp; - } else if (this->action == TB_ACTION_DOWN && lastEvent == TB_ACTION_DOWN) { - // LOG_DEBUG("Trackball event DOWN"); + } else if (down_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event DOWN %u", millis()); +#endif e.inputEvent = this->_eventDown; - } else if (this->action == TB_ACTION_LEFT && lastEvent == TB_ACTION_LEFT) { - // LOG_DEBUG("Trackball event LEFT"); + } else if (left_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event LEFT %u", millis()); +#endif e.inputEvent = this->_eventLeft; - } else if (this->action == TB_ACTION_RIGHT && lastEvent == TB_ACTION_RIGHT) { - // LOG_DEBUG("Trackball event RIGHT"); + } else if (right_counter >= TB_THRESHOLD) { +#ifdef INPUT_DEBUG + LOG_DEBUG("Trackball event RIGHT %u", millis()); +#endif e.inputEvent = this->_eventRight; } #else @@ -179,6 +203,12 @@ int32_t TrackballInterruptBase::runOnce() e.source = this->_originName; e.kbchar = 0x00; this->notifyObservers(&e); +#if TB_THRESHOLD + left_counter = 0; + right_counter = 0; + up_counter = 0; + down_counter = 0; +#endif } // Only update lastEvent for non-press actions or completed press actions @@ -194,25 +224,49 @@ int32_t TrackballInterruptBase::runOnce() void TrackballInterruptBase::intPressHandler() { - this->action = TB_ACTION_PRESSED; + if (!Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_PRESSED; + lastInterruptTime = millis(); } void TrackballInterruptBase::intDownHandler() { - this->action = TB_ACTION_DOWN; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_DOWN; + lastInterruptTime = millis(); + +#if TB_THRESHOLD + down_counter++; +#endif } void TrackballInterruptBase::intUpHandler() { - this->action = TB_ACTION_UP; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_UP; + lastInterruptTime = millis(); + +#if TB_THRESHOLD + up_counter++; +#endif } void TrackballInterruptBase::intLeftHandler() { - this->action = TB_ACTION_LEFT; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_LEFT; + lastInterruptTime = millis(); +#if TB_THRESHOLD + left_counter++; +#endif } void TrackballInterruptBase::intRightHandler() { - this->action = TB_ACTION_RIGHT; + if (TB_THRESHOLD || !Throttle::isWithinTimespanMs(lastInterruptTime, 10)) + this->action = TB_ACTION_RIGHT; + lastInterruptTime = millis(); +#if TB_THRESHOLD + right_counter++; +#endif } diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h index 67d4ee449..908f62769 100644 --- a/src/input/TrackballInterruptBase.h +++ b/src/input/TrackballInterruptBase.h @@ -12,6 +12,10 @@ #endif #endif +#ifndef TB_THRESHOLD +#define TB_THRESHOLD 0 +#endif + class TrackballInterruptBase : public Observable, public concurrency::OSThread { public: @@ -25,8 +29,6 @@ class TrackballInterruptBase : public Observable, public con void intUpHandler(); void intLeftHandler(); void intRightHandler(); - uint32_t lastTime = 0; - virtual int32_t runOnce() override; protected: @@ -67,4 +69,12 @@ class TrackballInterruptBase : public Observable, public con input_broker_event _eventPressedLong = INPUT_BROKER_NONE; const char *_originName; TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE; + volatile uint32_t lastInterruptTime = 0; + +#if TB_THRESHOLD + volatile uint8_t left_counter = 0; + volatile uint8_t right_counter = 0; + volatile uint8_t up_counter = 0; + volatile uint8_t down_counter = 0; +#endif }; diff --git a/src/input/TrackballInterruptImpl1.cpp b/src/input/TrackballInterruptImpl1.cpp index 594facdeb..fd126913a 100644 --- a/src/input/TrackballInterruptImpl1.cpp +++ b/src/input/TrackballInterruptImpl1.cpp @@ -24,41 +24,26 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe void TrackballInterruptImpl1::handleIntDown() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intDownHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intDownHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntUp() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intUpHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intUpHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntLeft() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intLeftHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intLeftHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntRight() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intRightHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intRightHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } void TrackballInterruptImpl1::handleIntPressed() { - if (TB_DIRECTION == RISING || millis() > trackballInterruptImpl1->lastTime + 10) { - trackballInterruptImpl1->lastTime = millis(); - trackballInterruptImpl1->intPressHandler(); - trackballInterruptImpl1->setIntervalFromNow(20); - } + trackballInterruptImpl1->intPressHandler(); + trackballInterruptImpl1->setIntervalFromNow(20); } diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h index 8d2996131..ab5b74870 100644 --- a/variants/esp32s3/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -71,6 +71,7 @@ #define TB_RIGHT 2 #define TB_PRESS 0 // BUTTON_PIN #define TB_DIRECTION FALLING +#define TB_THRESHOLD 3 // microphone #define ES7210_SCK 47 From 7efc3e37708cdaa0762baec12bd178b7760fa938 Mon Sep 17 00:00:00 2001 From: Keane <2617725+k3an3@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:32:03 -0500 Subject: [PATCH 021/223] Replace strcpy with strncpy and null termination (#9436) Co-authored-by: Jonathan Bennett --- src/mqtt/MQTT.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 4c2c0fe1b..18a4f913e 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -475,8 +475,10 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained) if (moduleConfig.mqtt.proxy_to_client_enabled) { meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed(); msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag; - strcpy(msg->topic, topic); - strcpy(msg->payload_variant.text, payload); + strncpy(msg->topic, topic, sizeof(msg->topic)); + msg->topic[sizeof(msg->topic) - 1] = '\0'; + strncpy(msg->payload_variant.text, payload, sizeof(msg->payload_variant.text)); + msg->payload_variant.text[sizeof(msg->payload_variant.text) - 1] = '\0'; msg->retained = retained; service->sendMqttMessageToClientProxy(msg); return true; From a2e8e232f11ee7660a5e92c8fd0d6c72439369d7 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 26 Jan 2026 16:58:16 -0600 Subject: [PATCH 022/223] Remove the unused OCV_ARRAYs and move one to a variant.h (#9442) --- src/power.h | 12 ------------ variants/esp32/chatter2/variant.h | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/power.h b/src/power.h index 5f887c36b..e4b456d3b 100644 --- a/src/power.h +++ b/src/power.h @@ -15,20 +15,8 @@ // Device specific curves go in variant.h #ifndef OCV_ARRAY -#ifdef CELL_TYPE_LIFEPO4 -#define OCV_ARRAY 3400, 3350, 3320, 3290, 3270, 3260, 3250, 3230, 3200, 3120, 3000 -#elif defined(CELL_TYPE_LEADACID) -#define OCV_ARRAY 2120, 2090, 2070, 2050, 2030, 2010, 1990, 1980, 1970, 1960, 1950 -#elif defined(CELL_TYPE_ALKALINE) -#define OCV_ARRAY 1580, 1400, 1350, 1300, 1280, 1250, 1230, 1190, 1150, 1100, 1000 -#elif defined(CELL_TYPE_NIMH) -#define OCV_ARRAY 1400, 1300, 1280, 1270, 1260, 1250, 1240, 1230, 1210, 1150, 1000 -#elif defined(CELL_TYPE_LTO) -#define OCV_ARRAY 2700, 2560, 2540, 2520, 2500, 2460, 2420, 2400, 2380, 2320, 1500 -#else // LiIon #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 #endif -#endif /*Note: 12V lead acid is 6 cells, most board accept only 1 cell LiIon/LiPo*/ #ifndef NUM_CELLS diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index 0c1ef6967..abcb1ce4d 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -79,7 +79,7 @@ // lower dB for lower voltage rnage #define ADC_MULTIPLIER 5.0 // VBATT---10k--pin34---2.5K---GND // Chatter2 uses 3 AAA cells -#define CELL_TYPE_ALKALINE +#define OCV_ARRAY 1580, 1400, 1350, 1300, 1280, 1250, 1230, 1190, 1150, 1100, 1000 #define NUM_CELLS 3 #undef EXT_PWR_DETECT From 63a97a54e110bd1d22f39812229becba0555da68 Mon Sep 17 00:00:00 2001 From: Colby Dillion Date: Mon, 26 Jan 2026 18:45:24 -0600 Subject: [PATCH 023/223] Fix retry_delay calculation for error responses (#9443) Co-authored-by: Ben Meadors --- src/modules/StoreForwardModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index b8a710bf5..023a1c798 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -513,7 +513,7 @@ bool StoreForwardModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, LOG_DEBUG("StoreAndForward_RequestResponse_ROUTER_BUSY"); // retry in messages_saved * packetTimeMax ms retry_delay = millis() + getNumAvailablePackets(this->busyTo, this->last_time) * packetTimeMax * - (meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1); + (p->rr == meshtastic_StoreAndForward_RequestResponse_ROUTER_ERROR ? 2 : 1); } break; From 90778a4e788b70442a705f43dd92916b9b028248 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Tue, 27 Jan 2026 20:03:31 +0800 Subject: [PATCH 024/223] feat(GPS): Support Softsleep with WAKE-UP pin on PA1010D (#9078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support softsleep by defining PIN_GPS_STANDBY on CDTop CD-PA1010D. This differs from existing MTK GPS e.g. L76K, where pulling PIN_GPS_STANDBY (WAKE-UP pin) low is not sufficient to put the GPS module in standby. An additional `$PMTK225,4*2F` must be sent to enter "Backup Mode", which is exited by bringing PIN_GPS_STANDBY (WAKE-UP pin) high. Refer to datasheet[0] §1.9.3 "Backup Mode". 0: https://cdn-learn.adafruit.com/assets/assets/000/084/295/original/CD_PA1010D_Datasheet_v.03.pdf Signed-off-by: Andrew Yong --- src/gps/GPS.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index fd121861c..13e5c32d1 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -905,6 +905,12 @@ void GPS::writePinStandby(bool standby) // Write and log pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, val); + + // Enter backup mode on PA1010D; TODO: may be applicable to other MTK GPS too + if (IS_ONE_OF(gnssModel, GNSS_MODEL_MTK_PA1010D)) { + _serial_gps->write("$PMTK225,4*2F\r\n"); + } + #ifdef GPS_DEBUG LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW"); #endif From c8079d4115abd5e4a3e2902e95e7de9224e0280c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 27 Jan 2026 08:05:36 -0600 Subject: [PATCH 025/223] Metadata for heltec tracker v2 --- .../esp32s3/heltec_wireless_tracker_v2/platformio.ini | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini index 8bdb71541..0b486618b 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -1,8 +1,17 @@ [env:heltec-wireless-tracker-v2] +custom_meshtastic_support_level = 1 +custom_meshtastic_images = heltec_wireless_tracker_v2.svg +custom_meshtastic_tags = Heltec + extends = esp32s3_base board = heltec_wireless_tracker_v2 board_build.partitions = default_8MB.csv upload_protocol = esptool +custom_meshtastic_hw_model = 113 +custom_meshtastic_hw_model_slug = HELTEC_WIRELESS_TRACKER_V2 +custom_meshtastic_architecture = esp32s3 +custom_meshtastic_display_name = Heltec Wireless Tracker V2 +custom_meshtastic_actively_supported = true build_flags = ${esp32s3_base.build_flags} From d54ae5dad8c84eab3b42e2b98d2c15d3c7788256 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:11:11 -0500 Subject: [PATCH 026/223] InkHUD Menu improvements (#8975) * InkHUD: Region Picker on initial setup * Added Node Config menu with Lora Region Picker * Role picker * Preset Picker * Timezone picker added * Power save mode and bluetooth configs * Config section Headers * Channel Config * Cleaning some behavior * Add back to all Options * Display config added * Position Toggle added * Network Config for ESP32 * Wifi details * Reduce line spacing to fit more content * Recent list with checkboxes * Timezone labels easier to understand * Trunk fix * Added "Saving Changes" screen when reboot is needed * Trunk fix * Make Tips show after first boot if the region is Unset * Added ResetDB and keep only favorite commands * quick fix to joystick * Trunk Fix * Fix to tips to work with new joystick input * Added ADC multiplier value display on power config * added ADC calibration feature * Fixed missing stray endiff * GPS toggle now is aware if gps is present. --- .../InkHUD/Applets/System/Logo/LogoApplet.cpp | 12 + .../InkHUD/Applets/System/Logo/LogoApplet.h | 1 + .../InkHUD/Applets/System/Menu/MenuAction.h | 79 ++ .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 1127 ++++++++++++++++- .../InkHUD/Applets/System/Menu/MenuApplet.h | 12 +- .../InkHUD/Applets/System/Menu/MenuItem.h | 7 + .../InkHUD/Applets/System/Menu/MenuPage.h | 19 +- .../InkHUD/Applets/System/Tips/TipsApplet.cpp | 192 ++- .../InkHUD/Applets/System/Tips/TipsApplet.h | 1 + src/graphics/niche/InkHUD/Events.cpp | 9 + src/graphics/niche/InkHUD/Events.h | 13 +- src/graphics/niche/InkHUD/InkHUD.cpp | 7 + src/graphics/niche/InkHUD/InkHUD.h | 4 + src/graphics/niche/InkHUD/SystemApplet.h | 1 + 14 files changed, 1344 insertions(+), 140 deletions(-) diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp index ecaa7cea3..4b55529bb 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp @@ -155,6 +155,18 @@ void InkHUD::LogoApplet::onShutdown() // This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete } +void InkHUD::LogoApplet::onApplyingChanges() +{ + bringToForeground(); + + textLeft = ""; + textRight = ""; + textTitle = "Applying changes"; + fontTitle = fontSmall; + + inkhud->forceUpdate(Drivers::EInk::FAST, false); +} + void InkHUD::LogoApplet::onReboot() { bringToForeground(); diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h index 3f604baed..37f940453 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h @@ -26,6 +26,7 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread void onBackground() override; void onShutdown() override; void onReboot() override; + void onApplyingChanges(); protected: int32_t runOnce() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h index debe2b719..74ad5c85f 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h @@ -22,6 +22,7 @@ enum MenuAction { STORE_CANNEDMESSAGE_SELECTION, SEND_CANNEDMESSAGE, SHUTDOWN, + BACK, NEXT_TILE, TOGGLE_BACKLIGHT, TOGGLE_GPS, @@ -36,6 +37,84 @@ enum MenuAction { TOGGLE_NOTIFICATIONS, TOGGLE_INVERT_COLOR, TOGGLE_12H_CLOCK, + // Regions + SET_REGION_US, + SET_REGION_EU_868, + SET_REGION_EU_433, + SET_REGION_CN, + SET_REGION_JP, + SET_REGION_ANZ, + SET_REGION_KR, + SET_REGION_TW, + SET_REGION_RU, + SET_REGION_IN, + SET_REGION_NZ_865, + SET_REGION_TH, + SET_REGION_LORA_24, + SET_REGION_UA_433, + SET_REGION_UA_868, + SET_REGION_MY_433, + SET_REGION_MY_919, + SET_REGION_SG_923, + SET_REGION_PH_433, + SET_REGION_PH_868, + SET_REGION_PH_915, + SET_REGION_ANZ_433, + SET_REGION_KZ_433, + SET_REGION_KZ_863, + SET_REGION_NP_865, + SET_REGION_BR_902, + // Device Roles + SET_ROLE_CLIENT, + SET_ROLE_CLIENT_MUTE, + SET_ROLE_ROUTER, + SET_ROLE_REPEATER, + // Presets + SET_PRESET_LONG_SLOW, + SET_PRESET_LONG_MODERATE, + SET_PRESET_LONG_FAST, + SET_PRESET_MEDIUM_SLOW, + SET_PRESET_MEDIUM_FAST, + SET_PRESET_SHORT_SLOW, + SET_PRESET_SHORT_FAST, + SET_PRESET_SHORT_TURBO, + // Timezones + SET_TZ_US_HAWAII, + SET_TZ_US_ALASKA, + SET_TZ_US_PACIFIC, + SET_TZ_US_ARIZONA, + SET_TZ_US_MOUNTAIN, + SET_TZ_US_CENTRAL, + SET_TZ_US_EASTERN, + SET_TZ_BR_BRAZILIA, + SET_TZ_UTC, + SET_TZ_EU_WESTERN, + SET_TZ_EU_CENTRAL, + SET_TZ_EU_EASTERN, + SET_TZ_ASIA_KOLKATA, + SET_TZ_ASIA_HONG_KONG, + SET_TZ_AU_AWST, + SET_TZ_AU_ACST, + SET_TZ_AU_AEST, + SET_TZ_PACIFIC_NZ, + // Power + TOGGLE_POWER_SAVE, + CALIBRATE_ADC, + // Bluetooth + TOGGLE_BLUETOOTH, + TOGGLE_BLUETOOTH_PAIR_MODE, + // Channel + TOGGLE_CHANNEL_UPLINK, + TOGGLE_CHANNEL_DOWNLINK, + TOGGLE_CHANNEL_POSITION, + SET_CHANNEL_PRECISION, + // Display + TOGGLE_DISPLAY_UNITS, + // Network + TOGGLE_WIFI, + // Administration + RESET_NODEDB_ALL, + RESET_NODEDB_KEEP_FAVORITES, }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 7e7093857..93d2c6b83 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -2,16 +2,21 @@ #include "./MenuApplet.h" -#include "RTC.h" - +#include "DisplayFormatters.h" +#include "GPS.h" #include "MeshService.h" +#include "RTC.h" #include "Router.h" #include "airtime.h" #include "main.h" +#include "mesh/generated/meshtastic/deviceonly.pb.h" #include "power.h" - -#if !MESHTASTIC_EXCLUDE_GPS -#include "GPS.h" +#include +#include +#if defined(ARCH_ESP32) && HAS_WIFI +#include "mesh/wifi/WiFiAPClient.h" +#include +#include #endif using namespace NicheGraphics; @@ -22,6 +27,18 @@ static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu // These are offered to users as possible values for settings.recentlyActiveSeconds static constexpr uint8_t RECENTS_OPTIONS_MINUTES[] = {2, 5, 10, 30, 60, 120}; +struct PositionPrecisionOption { + uint8_t value; // proto value + const char *metric; + const char *imperial; +}; + +static constexpr PositionPrecisionOption POSITION_PRECISION_OPTIONS[] = { + {32, "Precise", "Precise"}, {19, "50 m", "150 ft"}, {18, "90 m", "300 ft"}, {17, "200 m", "600 ft"}, + {16, "350 m", "0.2 mi"}, {15, "700 m", "0.5 mi"}, {14, "1.5 km", "0.9 mi"}, {13, "2.9 km", "1.8 mi"}, + {12, "5.8 km", "3.6 mi"}, {11, "12 km", "7.3 mi"}, {10, "23 km", "15 mi"}, +}; + InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet") { // No timer tasks at boot @@ -45,8 +62,15 @@ void InkHUD::MenuApplet::onForeground() // We do need this before we render, but we can optimize by just calculating it once now systemInfoPanelHeight = getSystemInfoPanelHeight(); - // Display initial menu page - showPage(MenuPage::ROOT); + // Force Region page ONLY when explicitly requested (one-shot) + if (inkhud->forceRegionMenu) { + + inkhud->forceRegionMenu = false; // consume one-shot flag + showPage(MenuPage::REGION); + + } else { + showPage(MenuPage::ROOT); + } // If device has a backlight which isn't controlled by aux button: // backlight on always when menu opens. @@ -139,6 +163,150 @@ int32_t InkHUD::MenuApplet::runOnce() return OSThread::disable(); } +static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region) +{ + if (config.lora.region == region) + return; + + config.lora.region = region; + + auto changes = SEGMENT_CONFIG; + +#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI) + if (!owner.is_licensed) { + bool keygenSuccess = false; + + if (config.security.private_key.size == 32) { + if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { + keygenSuccess = true; + } + } else { + crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); + keygenSuccess = true; + } + + if (keygenSuccess) { + config.security.public_key.size = 32; + config.security.private_key.size = 32; + owner.public_key.size = 32; + memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32); + } + } +#endif + + config.lora.tx_enabled = true; + + initRegion(); + + if (myRegion && myRegion->dutyCycle < 100) { + config.lora.ignore_mqtt = true; + } + + if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) { + sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); + changes |= SEGMENT_MODULECONFIG; + } + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + service->reloadConfig(changes); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static void applyDeviceRole(meshtastic_Config_DeviceConfig_Role role) +{ + if (config.device.role == role) + return; + + config.device.role = role; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + + service->reloadConfig(SEGMENT_CONFIG); + + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static void applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset preset) +{ + if (config.lora.modem_preset == preset) + return; + + config.lora.use_preset = true; + config.lora.modem_preset = preset; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); + + // Notify UI that changes are being applied + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; +} + +static const char *getTimezoneLabelFromValue(const char *tzdef) +{ + if (!tzdef || !*tzdef) + return "Unset"; + + // Must match TIMEZONE menu entries + if (strcmp(tzdef, "HST10") == 0) + return "US/Hawaii"; + if (strcmp(tzdef, "AKST9AKDT,M3.2.0,M11.1.0") == 0) + return "US/Alaska"; + if (strcmp(tzdef, "PST8PDT,M3.2.0,M11.1.0") == 0) + return "US/Pacific"; + if (strcmp(tzdef, "MST7") == 0) + return "US/Arizona"; + if (strcmp(tzdef, "MST7MDT,M3.2.0,M11.1.0") == 0) + return "US/Mountain"; + if (strcmp(tzdef, "CST6CDT,M3.2.0,M11.1.0") == 0) + return "US/Central"; + if (strcmp(tzdef, "EST5EDT,M3.2.0,M11.1.0") == 0) + return "US/Eastern"; + if (strcmp(tzdef, "BRT3") == 0) + return "BR/Brasilia"; + if (strcmp(tzdef, "UTC0") == 0) + return "UTC"; + if (strcmp(tzdef, "GMT0BST,M3.5.0/1,M10.5.0") == 0) + return "EU/Western"; + if (strcmp(tzdef, "CET-1CEST,M3.5.0,M10.5.0/3") == 0) + return "EU/Central"; + if (strcmp(tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4") == 0) + return "EU/Eastern"; + if (strcmp(tzdef, "IST-5:30") == 0) + return "Asia/Kolkata"; + if (strcmp(tzdef, "HKT-8") == 0) + return "Asia/Hong Kong"; + if (strcmp(tzdef, "AWST-8") == 0) + return "AU/AWST"; + if (strcmp(tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3") == 0) + return "AU/ACST"; + if (strcmp(tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3") == 0) + return "AU/AEST"; + if (strcmp(tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3") == 0) + return "Pacific/NZ"; + + return tzdef; // fallback for unknown/custom values +} + +static void applyTimezone(const char *tz) +{ + if (!tz || strcmp(config.device.tzdef, tz) == 0) + return; + + strncpy(config.device.tzdef, tz, sizeof(config.device.tzdef)); + config.device.tzdef[sizeof(config.device.tzdef) - 1] = '\0'; + + setenv("TZ", config.device.tzdef, 1); + + nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); +} + // Perform action for a menu item, then change page // Behaviors for MenuActions are defined here void InkHUD::MenuApplet::execute(MenuItem item) @@ -150,10 +318,22 @@ void InkHUD::MenuApplet::execute(MenuItem item) // Open a submenu without performing any action // Also handles exit case NO_ACTION: + if (currentPage == MenuPage::NODE_CONFIG_CHANNELS && item.nextPage == MenuPage::NODE_CONFIG_CHANNEL_DETAIL) { + + // cursor - 1 because index 0 is "Back" + selectedChannelIndex = cursor - 1; + } break; + case BACK: + showPage(item.nextPage); + return; + case NEXT_TILE: inkhud->nextTile(); + // Unselect menu item after tile change + cursorShown = false; + cursor = 0; break; case SEND_PING: @@ -196,17 +376,23 @@ void InkHUD::MenuApplet::execute(MenuItem item) break; case TOGGLE_APPLET: - settings->userApplets.active[cursor] = !settings->userApplets.active[cursor]; - inkhud->updateAppletSelection(); + if (item.checkState) { + *item.checkState = !(*item.checkState); + inkhud->updateAppletSelection(); + } break; case TOGGLE_AUTOSHOW_APPLET: // Toggle settings.userApplets.autoshow[] value, via MenuItem::checkState pointer set in populateAutoshowPage() - *items.at(cursor).checkState = !(*items.at(cursor).checkState); + if (item.checkState) { + *item.checkState = !(*item.checkState); + } break; case TOGGLE_NOTIFICATIONS: - settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications; + if (item.checkState) { + *item.checkState = !(*item.checkState); + } break; case TOGGLE_INVERT_COLOR: @@ -218,12 +404,14 @@ void InkHUD::MenuApplet::execute(MenuItem item) nodeDB->saveToDisk(SEGMENT_CONFIG); break; - case SET_RECENTS: - // Set value of settings.recentlyActiveSeconds - // Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file) - assert(cursor < sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0])); - settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes + case SET_RECENTS: { + // cursor - 1 because index 0 is "Back" + const uint8_t index = cursor - 1; + constexpr uint8_t optionCount = sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0]); + assert(index < optionCount); + settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[index] * 60; break; + } case SHUTDOWN: LOG_INFO("Shutting down from menu"); @@ -251,8 +439,18 @@ void InkHUD::MenuApplet::execute(MenuItem item) break; case TOGGLE_GPS: - gps->toggleGpsMode(); +#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; + } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { + config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; + } else { + // NOT_PRESENT do nothing + break; + } nodeDB->saveToDisk(SEGMENT_CONFIG); + service->reloadConfig(SEGMENT_CONFIG); +#endif break; case ENABLE_BLUETOOTH: @@ -260,10 +458,397 @@ void InkHUD::MenuApplet::execute(MenuItem item) LOG_INFO("Enabling Bluetooth"); config.network.wifi_enabled = false; config.bluetooth.enabled = true; - nodeDB->saveToDisk(); + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); rebootAtMsec = millis() + 2000; break; + // Power / Network (ESP32-only) +#if defined(ARCH_ESP32) + case TOGGLE_POWER_SAVE: + config.power.is_power_saving = !config.power.is_power_saving; + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case TOGGLE_WIFI: + config.network.wifi_enabled = !config.network.wifi_enabled; + + if (config.network.wifi_enabled) { + // Switch behavior: WiFi ON forces Bluetooth OFF + config.bluetooth.enabled = false; + } + + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; +#endif + // ADC Calibration + case CALIBRATE_ADC: { + // Read current measured voltage + float measuredV = powerStatus->getBatteryVoltageMv() / 1000.0f; + + // Sanity check + if (measuredV < 3.0f || measuredV > 4.5f) { + LOG_WARN("ADC calibration aborted, unreasonable voltage: %.2fV", measuredV); + break; + } + + // Determine the base multiplier currently in effect + float baseMult = 0.0f; + + if (config.power.adc_multiplier_override > 0.0f) { + baseMult = config.power.adc_multiplier_override; + } +#ifdef ADC_MULTIPLIER + else { + baseMult = ADC_MULTIPLIER; + } +#endif + + if (baseMult <= 0.0f) { + LOG_WARN("ADC calibration failed: no base multiplier"); + break; + } + + // Target voltage considered 100% by UI + constexpr float TARGET_VOLTAGE = 4.19f; + + // Calculate new multiplier + float newMult = baseMult * (TARGET_VOLTAGE / measuredV); + + config.power.adc_multiplier_override = newMult; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + + LOG_INFO("ADC calibrated: measured=%.3fV base=%.4f new=%.4f", measuredV, baseMult, newMult); + + break; + } + + // Display + case TOGGLE_DISPLAY_UNITS: + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC; + else + config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL; + + nodeDB->saveToDisk(SEGMENT_CONFIG); + break; + + // Bluetooth + case TOGGLE_BLUETOOTH: + config.bluetooth.enabled = !config.bluetooth.enabled; + + if (config.bluetooth.enabled) { + // Switch behavior: Bluetooth ON forces WiFi OFF + config.network.wifi_enabled = false; + } + + nodeDB->saveToDisk(SEGMENT_CONFIG); + InkHUD::InkHUD::getInstance()->notifyApplyingChanges(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case TOGGLE_BLUETOOTH_PAIR_MODE: + config.bluetooth.fixed_pin = !config.bluetooth.fixed_pin; + nodeDB->saveToDisk(SEGMENT_CONFIG); + break; + + // Regions + case SET_REGION_US: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_US); + break; + + case SET_REGION_EU_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_868); + break; + + case SET_REGION_EU_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_433); + break; + + case SET_REGION_CN: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_CN); + break; + + case SET_REGION_JP: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_JP); + break; + + case SET_REGION_ANZ: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_ANZ); + break; + case SET_REGION_KR: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KR); + break; + + case SET_REGION_TW: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_TW); + break; + + case SET_REGION_RU: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_RU); + break; + + case SET_REGION_IN: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_IN); + break; + + case SET_REGION_NZ_865: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_NZ_865); + break; + + case SET_REGION_TH: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_TH); + break; + + case SET_REGION_LORA_24: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_LORA_24); + break; + + case SET_REGION_UA_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_UA_433); + break; + + case SET_REGION_UA_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_UA_868); + break; + + case SET_REGION_MY_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_MY_433); + break; + + case SET_REGION_MY_919: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_MY_919); + break; + + case SET_REGION_SG_923: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_SG_923); + break; + + case SET_REGION_PH_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_433); + break; + + case SET_REGION_PH_868: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_868); + break; + + case SET_REGION_PH_915: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_PH_915); + break; + + case SET_REGION_ANZ_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_ANZ_433); + break; + + case SET_REGION_KZ_433: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KZ_433); + break; + + case SET_REGION_KZ_863: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_KZ_863); + break; + + case SET_REGION_NP_865: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_NP_865); + break; + + case SET_REGION_BR_902: + applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_BR_902); + break; + + // Roles + case SET_ROLE_CLIENT: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT); + break; + + case SET_ROLE_CLIENT_MUTE: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE); + break; + + case SET_ROLE_ROUTER: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_ROUTER); + break; + + case SET_ROLE_REPEATER: + applyDeviceRole(meshtastic_Config_DeviceConfig_Role_REPEATER); + break; + + // Presets + case SET_PRESET_LONG_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW); + break; + + case SET_PRESET_LONG_MODERATE: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE); + break; + + case SET_PRESET_LONG_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST); + break; + + case SET_PRESET_MEDIUM_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW); + break; + + case SET_PRESET_MEDIUM_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST); + break; + + case SET_PRESET_SHORT_SLOW: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW); + break; + + case SET_PRESET_SHORT_FAST: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST); + break; + + case SET_PRESET_SHORT_TURBO: + applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO); + break; + + // Timezones + case SET_TZ_US_HAWAII: + applyTimezone("HST10"); + break; + + case SET_TZ_US_ALASKA: + applyTimezone("AKST9AKDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_PACIFIC: + applyTimezone("PST8PDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_ARIZONA: + applyTimezone("MST7"); + break; + + case SET_TZ_US_MOUNTAIN: + applyTimezone("MST7MDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_CENTRAL: + applyTimezone("CST6CDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_US_EASTERN: + applyTimezone("EST5EDT,M3.2.0,M11.1.0"); + break; + + case SET_TZ_BR_BRAZILIA: + applyTimezone("BRT3"); + break; + + case SET_TZ_UTC: + applyTimezone("UTC0"); + break; + + case SET_TZ_EU_WESTERN: + applyTimezone("GMT0BST,M3.5.0/1,M10.5.0"); + break; + + case SET_TZ_EU_CENTRAL: + applyTimezone("CET-1CEST,M3.5.0,M10.5.0/3"); + break; + + case SET_TZ_EU_EASTERN: + applyTimezone("EET-2EEST,M3.5.0/3,M10.5.0/4"); + break; + + case SET_TZ_ASIA_KOLKATA: + applyTimezone("IST-5:30"); + break; + + case SET_TZ_ASIA_HONG_KONG: + applyTimezone("HKT-8"); + break; + + case SET_TZ_AU_AWST: + applyTimezone("AWST-8"); + break; + + case SET_TZ_AU_ACST: + applyTimezone("ACST-9:30ACDT,M10.1.0,M4.1.0/3"); + break; + + case SET_TZ_AU_AEST: + applyTimezone("AEST-10AEDT,M10.1.0,M4.1.0/3"); + break; + + case SET_TZ_PACIFIC_NZ: + applyTimezone("NZST-12NZDT,M9.5.0,M4.1.0/3"); + break; + + // Channels + case TOGGLE_CHANNEL_UPLINK: { + auto &ch = channels.getByIndex(selectedChannelIndex); + ch.settings.uplink_enabled = !ch.settings.uplink_enabled; + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case TOGGLE_CHANNEL_DOWNLINK: { + auto &ch = channels.getByIndex(selectedChannelIndex); + ch.settings.downlink_enabled = !ch.settings.downlink_enabled; + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case TOGGLE_CHANNEL_POSITION: { + auto &ch = channels.getByIndex(selectedChannelIndex); + + if (!ch.settings.has_module_settings) + ch.settings.has_module_settings = true; + + if (ch.settings.module_settings.position_precision > 0) + ch.settings.module_settings.position_precision = 0; + else + ch.settings.module_settings.position_precision = 13; // default + + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case SET_CHANNEL_PRECISION: { + auto &ch = channels.getByIndex(selectedChannelIndex); + + if (!ch.settings.has_module_settings) + ch.settings.has_module_settings = true; + + // Cursor - 1 because of "Back" + uint8_t index = cursor - 1; + + constexpr uint8_t optionCount = sizeof(POSITION_PRECISION_OPTIONS) / sizeof(POSITION_PRECISION_OPTIONS[0]); + + if (index < optionCount) { + ch.settings.module_settings.position_precision = POSITION_PRECISION_OPTIONS[index].value; + } + + nodeDB->saveToDisk(SEGMENT_CHANNELS); + service->reloadConfig(SEGMENT_CHANNELS); + break; + } + + case RESET_NODEDB_ALL: + InkHUD::getInstance()->notifyApplyingChanges(); + nodeDB->resetNodes(); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + + case RESET_NODEDB_KEEP_FAVORITES: + InkHUD::getInstance()->notifyApplyingChanges(); + nodeDB->resetNodes(1); + rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; + break; + default: LOG_WARN("Action not implemented"); } @@ -279,6 +864,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) { items.clear(); items.shrink_to_fit(); + nodeConfigLabels.clear(); switch (page) { case ROOT: @@ -289,6 +875,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) items.push_back(MenuItem("Send", MenuPage::SEND)); items.push_back(MenuItem("Options", MenuPage::OPTIONS)); // items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO + items.push_back(MenuItem("Node Config", MenuPage::NODE_CONFIG)); items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::EXIT; @@ -305,6 +892,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) break; case OPTIONS: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT)); // Optional: backlight if (settings->optionalMenuItems.backlight) items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label @@ -312,16 +900,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) MenuPage::EXIT // Exit once complete )); - // Optional: GPS - if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) - items.push_back(MenuItem("Enable GPS", MenuAction::TOGGLE_GPS, MenuPage::EXIT)); - if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) - items.push_back(MenuItem("Disable GPS", MenuAction::TOGGLE_GPS, MenuPage::EXIT)); - - // Optional: Enable Bluetooth, in case of lost wifi connection - if (!config.bluetooth.enabled || config.network.wifi_enabled) - items.push_back(MenuItem("Enable Bluetooth", MenuAction::ENABLE_BLUETOOTH, MenuPage::EXIT)); - + // Options Toggles items.push_back(MenuItem("Applets", MenuPage::APPLETS)); items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW)); items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS)); @@ -334,33 +913,423 @@ void InkHUD::MenuApplet::showPage(MenuPage page) &settings->optionalFeatures.notifications)); items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings->optionalFeatures.batteryIcon)); - invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors)); - - items.push_back( - MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::ROOT; break; case APPLETS: - populateAppletPage(); + populateAppletPage(); // must be first + items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; break; case AUTOSHOW: - populateAutoshowPage(); + populateAutoshowPage(); // must be first + items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; break; case RECENTS: - populateRecentsPage(); - previousPage = MenuPage::OPTIONS; + populateRecentsPage(); // builds only the options + items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; + case NODE_CONFIG: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT)); + // Radio Config Section + items.push_back(MenuItem::Header("Radio Config")); + items.push_back(MenuItem("LoRa", MenuPage::NODE_CONFIG_LORA)); + items.push_back(MenuItem("Channel", MenuPage::NODE_CONFIG_CHANNELS)); + // Device Config Section + items.push_back(MenuItem::Header("Device Config")); + items.push_back(MenuItem("Device", MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Position", MenuPage::NODE_CONFIG_POSITION)); + items.push_back(MenuItem("Power", MenuPage::NODE_CONFIG_POWER)); +#if defined(ARCH_ESP32) + items.push_back(MenuItem("Network", MenuPage::NODE_CONFIG_NETWORK)); +#endif + items.push_back(MenuItem("Display", MenuPage::NODE_CONFIG_DISPLAY)); + items.push_back(MenuItem("Bluetooth", MenuPage::NODE_CONFIG_BLUETOOTH)); + + // Administration Section + items.push_back(MenuItem::Header("Administration")); + items.push_back(MenuItem("Reset NodeDB", MenuPage::NODE_CONFIG_ADMIN_RESET)); + + // Exit + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case NODE_CONFIG_DEVICE: { + + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + const char *role = DisplayFormatters::getDeviceRole(config.device.role); + nodeConfigLabels.emplace_back("Role: " + std::string(role)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_DEVICE_ROLE)); + + const char *tzLabel = getTimezoneLabelFromValue(config.device.tzdef); + nodeConfigLabels.emplace_back("Timezone: " + std::string(tzLabel)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::TIMEZONE)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POSITION: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); +#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS + const auto mode = config.position.gps_mode; + if (mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { + items.push_back(MenuItem("GPS None", MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POSITION)); + } else { + gpsEnabled = (mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED); + items.push_back(MenuItem("GPS", MenuAction::TOGGLE_GPS, MenuPage::NODE_CONFIG_POSITION, &gpsEnabled)); + } +#endif + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POWER: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); +#if defined(ARCH_ESP32) + items.push_back(MenuItem("Powersave", MenuAction::TOGGLE_POWER_SAVE, MenuPage::EXIT, &config.power.is_power_saving)); +#endif + // ADC Multiplier + float effectiveMult = 0.0f; + + // User override always shows if it exists + if (config.power.adc_multiplier_override > 0.0f) { + effectiveMult = config.power.adc_multiplier_override; + } +#ifdef ADC_MULTIPLIER + else { + // Fallback to variant defined + effectiveMult = ADC_MULTIPLIER; + } +#endif + + // Only show if we actually have a value + if (effectiveMult > 0.0f) { + char buf[32]; + snprintf(buf, sizeof(buf), "ADC Mult: %.3f", effectiveMult); + nodeConfigLabels.emplace_back(buf); + + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POWER_ADC_CAL)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_POWER_ADC_CAL: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_POWER)); + + // Instruction text (header-style, non-selectable) + items.push_back(MenuItem::Header("Run on full charge Only")); + + // Action + items.push_back(MenuItem("Calibrate ADC", MenuAction::CALIBRATE_ADC, MenuPage::NODE_CONFIG_POWER)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_NETWORK: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + const char *wifiLabel = config.network.wifi_enabled ? "WiFi: On" : "WiFi: Off"; + + items.push_back(MenuItem(wifiLabel, MenuAction::TOGGLE_WIFI, MenuPage::EXIT)); + +#if HAS_WIFI && defined(ARCH_ESP32) + if (config.network.wifi_enabled) { + + // Status + if (WiFi.status() == WL_CONNECTED) { + nodeConfigLabels.emplace_back("Status: Connected"); + } else { + nodeConfigLabels.emplace_back("Status: Not Connected"); + } + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + + // Signal + if (WiFi.status() == WL_CONNECTED) { + int rssi = WiFi.RSSI(); + int quality = constrain(2 * (rssi + 100), 0, 100); + + char sigBuf[32]; + snprintf(sigBuf, sizeof(sigBuf), "Signal: %d%%", quality); + nodeConfigLabels.emplace_back(sigBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + + char ipBuf[64]; + snprintf(ipBuf, sizeof(ipBuf), "IP: %s", WiFi.localIP().toString().c_str()); + nodeConfigLabels.emplace_back(ipBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + + // SSID + if (config.network.wifi_ssid && strlen(config.network.wifi_ssid) > 0) { + std::string ssidLabel = "SSID: "; + ssidLabel += config.network.wifi_ssid; + nodeConfigLabels.emplace_back(ssidLabel); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + + // Hostname + const char *host = WiFi.getHostname(); + if (host && strlen(host) > 0) { + std::string hostLabel = "Host: "; + hostLabel += host; + nodeConfigLabels.emplace_back(hostLabel); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_NETWORK)); + } + } +#endif + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_DISPLAY: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY, + &config.display.use_12h_clock)); + + const char *unitsLabel = + (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? "Units: Imperial" : "Units: Metric"; + + items.push_back(MenuItem(unitsLabel, MenuAction::TOGGLE_DISPLAY_UNITS, MenuPage::NODE_CONFIG_DISPLAY)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_BLUETOOTH: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + const char *btLabel = config.bluetooth.enabled ? "Bluetooth: On" : "Bluetooth: Off"; + items.push_back(MenuItem(btLabel, MenuAction::TOGGLE_BLUETOOTH, MenuPage::EXIT)); + + const char *pairLabel = config.bluetooth.fixed_pin ? "Pair Mode: Fixed" : "Pair Mode: Random"; + items.push_back(MenuItem(pairLabel, MenuAction::TOGGLE_BLUETOOTH_PAIR_MODE, MenuPage::NODE_CONFIG_BLUETOOTH)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_LORA: { + + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + const char *region = myRegion ? myRegion->name : "Unset"; + nodeConfigLabels.emplace_back("Region: " + std::string(region)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::REGION)); + + const char *preset = + DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset); + nodeConfigLabels.emplace_back("Preset: " + std::string(preset)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_PRESET)); + + char freqBuf[32]; + float freq = RadioLibInterface::instance->getFreq(); + snprintf(freqBuf, sizeof(freqBuf), "Freq: %.3f MHz", freq); + nodeConfigLabels.emplace_back(freqBuf); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_LORA)); + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNELS: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + + for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { + meshtastic_Channel &ch = channels.getByIndex(i); + + if (!ch.has_settings) + continue; + + if (ch.role == meshtastic_Channel_Role_DISABLED) + continue; + + std::string label = "#"; + + if (ch.role == meshtastic_Channel_Role_PRIMARY) { + label += "Primary"; + } else if (strlen(ch.settings.name) > 0) { + label += parse(ch.settings.name); + } else { + label += "Channel" + to_string(i + 1); + } + + nodeConfigLabels.push_back(label); + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNEL_DETAIL: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNELS)); + + meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); + + // Name (read-only) + const char *name = strlen(ch.settings.name) > 0 ? ch.settings.name : "Unnamed"; + nodeConfigLabels.emplace_back("Ch: " + parse(name)); + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + + // Uplink + items.push_back(MenuItem("Uplink", MenuAction::TOGGLE_CHANNEL_UPLINK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &ch.settings.uplink_enabled)); + + items.push_back(MenuItem("Downlink", MenuAction::TOGGLE_CHANNEL_DOWNLINK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &ch.settings.downlink_enabled)); + + // Position + channelPositionEnabled = ch.settings.has_module_settings && ch.settings.module_settings.position_precision > 0; + + items.push_back(MenuItem("Position", MenuAction::TOGGLE_CHANNEL_POSITION, MenuPage::NODE_CONFIG_CHANNEL_DETAIL, + &channelPositionEnabled)); + + // Precision + if (channelPositionEnabled) { + + std::string precisionLabel = "Unknown"; + + for (const auto &opt : POSITION_PRECISION_OPTIONS) { + if (opt.value == ch.settings.module_settings.position_precision) { + precisionLabel = (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) + ? opt.imperial + : opt.metric; + break; + } + } + nodeConfigLabels.emplace_back("Precision: " + precisionLabel); + items.push_back( + MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_CHANNEL_PRECISION)); + } + + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_CHANNEL_PRECISION: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); + if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) { + items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + break; + } + constexpr uint8_t optionCount = sizeof(POSITION_PRECISION_OPTIONS) / sizeof(POSITION_PRECISION_OPTIONS[0]); + for (uint8_t i = 0; i < optionCount; i++) { + const auto &opt = POSITION_PRECISION_OPTIONS[i]; + const char *label = + (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? opt.imperial : opt.metric; + nodeConfigLabels.emplace_back(label); + + items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_CHANNEL_PRECISION, + MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + } + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case NODE_CONFIG_DEVICE_ROLE: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Client", MenuAction::SET_ROLE_CLIENT, MenuPage::EXIT)); + items.push_back(MenuItem("Client Mute", MenuAction::SET_ROLE_CLIENT_MUTE, MenuPage::EXIT)); + items.push_back(MenuItem("Router", MenuAction::SET_ROLE_ROUTER, MenuPage::EXIT)); + items.push_back(MenuItem("Repeater", MenuAction::SET_ROLE_REPEATER, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + + case TIMEZONE: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Hawaii", SET_TZ_US_HAWAII, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Alaska", SET_TZ_US_ALASKA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Pacific", SET_TZ_US_PACIFIC, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Arizona", SET_TZ_US_ARIZONA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Mountain", SET_TZ_US_MOUNTAIN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Central", SET_TZ_US_CENTRAL, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("US/Eastern", SET_TZ_US_EASTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("BR/Brasilia", SET_TZ_BR_BRAZILIA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("UTC", SET_TZ_UTC, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Western", SET_TZ_EU_WESTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Central", SET_TZ_EU_CENTRAL, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("EU/Eastern", SET_TZ_EU_EASTERN, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Asia/Kolkata", SET_TZ_ASIA_KOLKATA, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Asia/Hong Kong", SET_TZ_ASIA_HONG_KONG, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/AWST", SET_TZ_AU_AWST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/ACST", SET_TZ_AU_ACST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("AU/AEST", SET_TZ_AU_AEST, MenuPage::NODE_CONFIG_DEVICE)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case REGION: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA)); + items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT)); + items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT)); + items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT)); + items.push_back(MenuItem("CN", MenuAction::SET_REGION_CN, MenuPage::EXIT)); + items.push_back(MenuItem("JP", MenuAction::SET_REGION_JP, MenuPage::EXIT)); + items.push_back(MenuItem("ANZ", MenuAction::SET_REGION_ANZ, MenuPage::EXIT)); + items.push_back(MenuItem("KR", MenuAction::SET_REGION_KR, MenuPage::EXIT)); + items.push_back(MenuItem("TW", MenuAction::SET_REGION_TW, MenuPage::EXIT)); + items.push_back(MenuItem("RU", MenuAction::SET_REGION_RU, MenuPage::EXIT)); + items.push_back(MenuItem("IN", MenuAction::SET_REGION_IN, MenuPage::EXIT)); + items.push_back(MenuItem("NZ 865", MenuAction::SET_REGION_NZ_865, MenuPage::EXIT)); + items.push_back(MenuItem("TH", MenuAction::SET_REGION_TH, MenuPage::EXIT)); + items.push_back(MenuItem("LoRa 2.4", MenuAction::SET_REGION_LORA_24, MenuPage::EXIT)); + items.push_back(MenuItem("UA 433", MenuAction::SET_REGION_UA_433, MenuPage::EXIT)); + items.push_back(MenuItem("UA 868", MenuAction::SET_REGION_UA_868, MenuPage::EXIT)); + items.push_back(MenuItem("MY 433", MenuAction::SET_REGION_MY_433, MenuPage::EXIT)); + items.push_back(MenuItem("MY 919", MenuAction::SET_REGION_MY_919, MenuPage::EXIT)); + items.push_back(MenuItem("SG 923", MenuAction::SET_REGION_SG_923, MenuPage::EXIT)); + items.push_back(MenuItem("PH 433", MenuAction::SET_REGION_PH_433, MenuPage::EXIT)); + items.push_back(MenuItem("PH 868", MenuAction::SET_REGION_PH_868, MenuPage::EXIT)); + items.push_back(MenuItem("PH 915", MenuAction::SET_REGION_PH_915, MenuPage::EXIT)); + items.push_back(MenuItem("ANZ 433", MenuAction::SET_REGION_ANZ_433, MenuPage::EXIT)); + items.push_back(MenuItem("KZ 433", MenuAction::SET_REGION_KZ_433, MenuPage::EXIT)); + items.push_back(MenuItem("KZ 863", MenuAction::SET_REGION_KZ_863, MenuPage::EXIT)); + items.push_back(MenuItem("NP 865", MenuAction::SET_REGION_NP_865, MenuPage::EXIT)); + items.push_back(MenuItem("BR 902", MenuAction::SET_REGION_BR_902, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + case NODE_CONFIG_PRESET: { + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA)); + items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT)); + items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT)); + items.push_back(MenuItem("Medium Fast", MenuAction::SET_PRESET_MEDIUM_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Short Slow", MenuAction::SET_PRESET_SHORT_SLOW, MenuPage::EXIT)); + items.push_back(MenuItem("Short Fast", MenuAction::SET_PRESET_SHORT_FAST, MenuPage::EXIT)); + items.push_back(MenuItem("Short Turbo", MenuAction::SET_PRESET_SHORT_TURBO, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + } + // Administration Section + case NODE_CONFIG_ADMIN_RESET: + items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + items.push_back(MenuItem("Reset All", MenuAction::RESET_NODEDB_ALL, MenuPage::EXIT)); + items.push_back(MenuItem("Keep Favorites Only", MenuAction::RESET_NODEDB_KEEP_FAVORITES, MenuPage::EXIT)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); + break; + + // Exit case EXIT: sendToBackground(); // Menu applet dismissed, allow normal behavior to resume break; @@ -379,6 +1348,15 @@ void InkHUD::MenuApplet::showPage(MenuPage page) cursorShown = false; } + // Ensure cursor never rests on a header + if (cursorShown) { + while (cursor < items.size() && items.at(cursor).isHeader) { + cursor++; + } + if (cursor >= items.size()) + cursor = 0; + } + // Remember which page we are on now currentPage = page; } @@ -390,7 +1368,8 @@ void InkHUD::MenuApplet::onRender() // Dimensions for the slots where we will draw menuItems const float padding = 0.05; - const uint16_t itemH = fontSmall.lineHeight() * 2; + const uint16_t itemH = fontSmall.lineHeight() * 1.6; + const int16_t selectInsetY = 2; const int16_t itemW = width() - X(padding) - X(padding); const int16_t itemL = X(padding); const int16_t itemR = X(1 - padding); @@ -441,18 +1420,31 @@ void InkHUD::MenuApplet::onRender() // -- Loop: draw each (visible) menu item -- for (uint8_t i = firstItem; i <= lastItem; i++) { - // Grab the menuItem - MenuItem item = items.at(i); - // Center-line for the text + // Grab the menu item + MenuItem &item = items.at(i); + + // Vertical center of this slot int16_t center = itemT + (itemH / 2); - // Box, if currently selected - if (cursorShown && i == cursor) - drawRect(itemL, itemT, itemW, itemH, BLACK); + // Header (non-selectable section label) + if (item.isHeader) { + setFont(fontSmall); - // Item's text - printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE); + // Header text (flush left) + printAt(itemL + X(padding), center, item.label, LEFT, MIDDLE); + + // Subtle underline + int16_t underlineY = itemT + itemH - 2; + drawLine(itemL + X(padding), underlineY, itemR - X(padding), underlineY, BLACK); + } else { + // Box, if currently selected + if (cursorShown && i == cursor) + drawRect(itemL, itemT + selectInsetY, itemW, itemH - (selectInsetY * 2), BLACK); + + // Indented normal item text + printAt(itemL + X(padding * 2), center, item.label, LEFT, MIDDLE); + } // Checkbox, if relevant if (item.checkState) { @@ -493,11 +1485,14 @@ void InkHUD::MenuApplet::onButtonShortPress() OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); if (!settings->joystick.enabled) { - // Move menu cursor to next entry, then update - if (cursorShown) - cursor = (cursor + 1) % items.size(); - else + if (!cursorShown) { cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); } else { if (cursorShown) @@ -538,14 +1533,17 @@ void InkHUD::MenuApplet::onNavUp() { OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Move menu cursor to previous entry, then update - if (cursor == 0) - cursor = items.size() - 1; - else - cursor--; - - if (!cursorShown) + if (!cursorShown) { cursorShown = true; + cursor = 0; + } else { + do { + if (cursor == 0) + cursor = items.size() - 1; + else + cursor--; + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); } @@ -554,11 +1552,14 @@ void InkHUD::MenuApplet::onNavDown() { OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Move menu cursor to next entry, then update - if (cursorShown) - cursor = (cursor + 1) % items.size(); - else + if (!cursorShown) { cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); } @@ -622,7 +1623,8 @@ void InkHUD::MenuApplet::populateRecentsPage() // (Defined at top of this file) for (uint8_t i = 0; i < optionCount; i++) { std::string label = to_string(RECENTS_OPTIONS_MINUTES[i]) + " mins"; - items.push_back(MenuItem(label.c_str(), MenuAction::SET_RECENTS, MenuPage::EXIT)); + recentsSelected[i] = (settings->recentlyActiveSeconds == RECENTS_OPTIONS_MINUTES[i] * 60); + items.push_back(MenuItem(label.c_str(), MenuAction::SET_RECENTS, MenuPage::OPTIONS, &recentsSelected[i])); } } @@ -873,5 +1875,4 @@ void InkHUD::MenuApplet::freeCannedMessageResources() cm.messageItems.clear(); cm.recipientItems.clear(); } - -#endif +#endif // MESHTASTIC_INCLUDE_INKHUD \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h index 4f9f92227..82ccc8f45 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h @@ -35,6 +35,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void onRender() override; void show(Tile *t); // Open the menu, onto a user tile + void setStartPage(MenuPage page); protected: Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton @@ -56,6 +57,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data + MenuPage startPageOverride = MenuPage::ROOT; MenuPage currentPage = MenuPage::ROOT; MenuPage previousPage = MenuPage::EXIT; uint8_t cursor = 0; // Which menu item is currently highlighted @@ -63,7 +65,15 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread uint16_t systemInfoPanelHeight = 0; // Need to know before we render - std::vector items; // MenuItems for the current page. Filled by ShowPage + std::vector items; // MenuItems for the current page. Filled by ShowPage + std::vector nodeConfigLabels; // Persistent labels for Node Config pages + uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel) + bool channelPositionEnabled = false; + bool gpsEnabled = false; + + // Recents menu checkbox state (derived from settings.recentlyActiveSeconds) + static constexpr uint8_t RECENTS_COUNT = 6; + bool recentsSelected[RECENTS_COUNT] = {}; // Data for selecting and sending canned messages via the menu // Placed into a sub-class for organization only diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h index c74fe3d8a..51c9161a7 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuItem.h @@ -30,6 +30,7 @@ class MenuItem MenuAction action = NO_ACTION; MenuPage nextPage = EXIT; bool *checkState = nullptr; + bool isHeader = false; // Non-selectable section label // Various constructors, depending on the intended function of the item @@ -40,6 +41,12 @@ class MenuItem : label(label), action(action), nextPage(nextPage), checkState(checkState) { } + static MenuItem Header(const char *label) + { + MenuItem item(label, NO_ACTION, EXIT); + item.isHeader = true; + return item; + } }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h index 389e411c3..138c389be 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuPage.h @@ -20,10 +20,27 @@ enum MenuPage : uint8_t { SEND, CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message OPTIONS, + NODE_CONFIG, + NODE_CONFIG_LORA, + NODE_CONFIG_CHANNELS, // List of channels + NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options + NODE_CONFIG_CHANNEL_PRECISION, + NODE_CONFIG_PRESET, + NODE_CONFIG_DEVICE, + NODE_CONFIG_DEVICE_ROLE, + NODE_CONFIG_POWER, + NODE_CONFIG_POWER_ADC_CAL, + NODE_CONFIG_NETWORK, + NODE_CONFIG_DISPLAY, + NODE_CONFIG_BLUETOOTH, + NODE_CONFIG_POSITION, + NODE_CONFIG_ADMIN_RESET, + TIMEZONE, APPLETS, AUTOSHOW, RECENTS, // Select length of "recentlyActiveSeconds" - EXIT, // Dismiss the menu applet + REGION, + EXIT, // Dismiss the menu applet }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp index a9d579873..7869319fe 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp @@ -10,34 +10,37 @@ using namespace NicheGraphics; InkHUD::TipsApplet::TipsApplet() { - // Decide which tips (if any) should be shown to user after the boot screen + bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); + + bool showTutorialTips = (settings->tips.firstBoot || needsRegion); // Welcome screen - if (settings->tips.firstBoot) + if (showTutorialTips) tipQueue.push_back(Tip::WELCOME); - // Antenna, region, timezone - // Shown at boot if region not yet set - if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) + // Finish setup + if (needsRegion) tipQueue.push_back(Tip::FINISH_SETUP); + // Using the UI + if (showTutorialTips) { + tipQueue.push_back(Tip::CUSTOMIZATION); + tipQueue.push_back(Tip::BUTTONS); + } + // Shutdown info // Shown until user performs one valid shutdown if (!settings->tips.safeShutdownSeen) tipQueue.push_back(Tip::SAFE_SHUTDOWN); - // Using the UI - if (settings->tips.firstBoot) { - tipQueue.push_back(Tip::CUSTOMIZATION); - tipQueue.push_back(Tip::BUTTONS); - } - // Catch an incorrect attempt at rotating display if (config.display.flip_screen) tipQueue.push_back(Tip::ROTATION); - // Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground - // LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector + // Region picker + if (needsRegion) + tipQueue.push_back(Tip::PICK_REGION); + if (!tipQueue.empty()) bringToForeground(); } @@ -51,81 +54,109 @@ void InkHUD::TipsApplet::onRender() case Tip::FINISH_SETUP: { setFont(fontMedium); - printAt(0, 0, "Tip: Finish Setup"); + const char *title = "Tip: Finish Setup"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - int16_t cursorY = fontMedium.lineHeight() * 1.5; - printAt(0, cursorY, "- connect antenna"); + int16_t cursorY = h + fontSmall.lineHeight(); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- connect a client app"); + auto drawBullet = [&](const char *text) { + uint16_t bh = getWrappedTextHeight(0, width(), text); + printWrapped(0, cursorY, width(), text); + cursorY += bh + (fontSmall.lineHeight() / 3); + }; - // Only if region not set - if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- set region"); - } + drawBullet("- connect antenna"); + drawBullet("- connect a client app"); - // Only if tz not set - if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) { - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- set timezone"); - } + if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) + drawBullet("- set region"); - cursorY += fontSmall.lineHeight() * 1.5; - printAt(0, cursorY, "More info at meshtastic.org"); + if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) + drawBullet("- set timezone"); + + cursorY += fontSmall.lineHeight() / 2; + drawBullet("More info at meshtastic.org"); - setFont(fontSmall); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); } break; + case Tip::PICK_REGION: { + setFont(fontMedium); + printAt(0, 0, "Set Region"); + + setFont(fontSmall); + printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup."); + + printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM); + } break; + case Tip::SAFE_SHUTDOWN: { setFont(fontMedium); - printAt(0, 0, "Tip: Shutdown"); + + const char *title = "Tip: Shutdown"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - std::string shutdown; - shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n"; - shutdown += "\n"; - shutdown += "This ensures data is saved."; - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n" + "This ensures data is saved."; + + uint16_t bodyH = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bodyH + (fontSmall.lineHeight() / 2); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); - } break; case Tip::CUSTOMIZATION: { setFont(fontMedium); - printAt(0, 0, "Tip: Customization"); + + const char *title = "Tip: Customization"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), - "Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more."); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "Configure & control display with the InkHUD menu. " + "Optional features, layout, rotation, and more."; + + uint16_t bodyH = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bodyH + (fontSmall.lineHeight() / 2); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); } break; case Tip::BUTTONS: { setFont(fontMedium); - printAt(0, 0, "Tip: Buttons"); + + const char *title = "Tip: Buttons"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); - int16_t cursorY = fontMedium.lineHeight() * 1.5; + int16_t cursorY = h + fontSmall.lineHeight(); + + auto drawBullet = [&](const char *text) { + uint16_t bh = getWrappedTextHeight(0, width(), text); + printWrapped(0, cursorY, width(), text); + cursorY += bh + (fontSmall.lineHeight() / 3); + }; if (!settings->joystick.enabled) { - printAt(0, cursorY, "User Button"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- short press: next"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- long press: select / open menu"); + drawBullet("User Button"); + drawBullet("- short press: next"); + drawBullet("- long press: select or open menu"); } else { - printAt(0, cursorY, "Joystick"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- open menu / select"); - cursorY += fontSmall.lineHeight() * 1.5; - printAt(0, cursorY, "Exit Button"); - cursorY += fontSmall.lineHeight() * 1.2; - printAt(0, cursorY, "- switch tile / close menu"); + drawBullet("Joystick"); + drawBullet("- press: open menu or select"); + drawBullet("Exit Button"); + drawBullet("- press: switch tile or close menu"); } printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); @@ -133,12 +164,21 @@ void InkHUD::TipsApplet::onRender() case Tip::ROTATION: { setFont(fontMedium); - printAt(0, 0, "Tip: Rotation"); + + const char *title = "Tip: Rotation"; + uint16_t h = getWrappedTextHeight(0, width(), title); + printWrapped(0, 0, width(), title); setFont(fontSmall); if (!settings->joystick.enabled) { - printWrapped(0, fontMedium.lineHeight() * 1.5, width(), - "To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate."); + int16_t cursorY = h + fontSmall.lineHeight(); + + const char *body = "To rotate the display, use the InkHUD menu. " + "Long-press the user button > Options > Rotate."; + + uint16_t bh = getWrappedTextHeight(0, width(), body); + printWrapped(0, cursorY, width(), body); + cursorY += bh + (fontSmall.lineHeight() / 2); } else { printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate."); @@ -159,12 +199,15 @@ void InkHUD::TipsApplet::renderWelcome() { uint16_t padW = X(0.05); + // Detect portrait orientation + bool portrait = height() > width(); + // Block 1 - logo & title // ======================== // Logo size - uint16_t logoWLimit = X(0.3); - uint16_t logoHLimit = Y(0.3); + uint16_t logoWLimit = portrait ? X(0.5) : X(0.3); + uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3); uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit); uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit); @@ -177,7 +220,7 @@ void InkHUD::TipsApplet::renderWelcome() // Center the block // Desired effect: equal margin from display edge for logo left and title right - int16_t block1Y = Y(0.3); + int16_t block1Y = portrait ? Y(0.2) : Y(0.3); int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2); int16_t logoCX = block1CX - (logoW / 2) - (padW / 2); int16_t titleCX = block1CX + (titleW / 2) + (padW / 2); @@ -192,7 +235,7 @@ void InkHUD::TipsApplet::renderWelcome() std::string subtitle = "InkHUD"; if (width() >= 200) subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display - printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE); + printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE); // Block 3 - press to continue // ============================ @@ -224,26 +267,37 @@ void InkHUD::TipsApplet::onBackground() // While our SystemApplet::handleInput flag is true void InkHUD::TipsApplet::onButtonShortPress() { + bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); + // If we're prompting the user to pick a region, hand off to the menu + if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) { + tipQueue.pop_front(); + + // Signal InkHUD to open the menu on Region page + inkhud->forceRegionMenu = true; + + // Close tips and open menu + sendToBackground(); + inkhud->openMenu(); + return; + } + // Consume current tip tipQueue.pop_front(); // All tips done if (tipQueue.empty()) { // Record that user has now seen the "tutorial" set of tips // Don't show them on subsequent boots - if (settings->tips.firstBoot) { + if (settings->tips.firstBoot && !needsRegion) { settings->tips.firstBoot = false; inkhud->persistence->saveSettings(); } - // Close applet, and full refresh to clean the screen - // Need to force update, because our request would be ignored otherwise, as we are now background + // Close applet and clean the screen sendToBackground(); inkhud->forceUpdate(EInk::UpdateTypes::FULL); - } - - // More tips left - else + } else { requestUpdate(); + } } // Functions the same as the user button in this instance diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h index 159e6f58f..ff7eea046 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h @@ -23,6 +23,7 @@ class TipsApplet : public SystemApplet enum class Tip { WELCOME, FINISH_SETUP, + PICK_REGION, SAFE_SHUTDOWN, CUSTOMIZATION, BUTTONS, diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index 5382d2391..fa45a49ed 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -276,6 +276,15 @@ int InkHUD::Events::beforeDeepSleep(void *unused) return 0; // We agree: deep sleep now } +// Display an intermediate screen while configuration changes are applied +void InkHUD::Events::applyingChanges() +{ + // Bring the logo applet forward with a temporary message + for (SystemApplet *sa : inkhud->systemApplets) { + sa->onApplyingChanges(); + } +} + // Callback for rebootObserver // Same as shutdown, without drawing the logoApplet // Makes sure we don't lose message history / InkHUD config diff --git a/src/graphics/niche/InkHUD/Events.h b/src/graphics/niche/InkHUD/Events.h index 664ca19f0..1916cf78e 100644 --- a/src/graphics/niche/InkHUD/Events.h +++ b/src/graphics/niche/InkHUD/Events.h @@ -29,12 +29,13 @@ class Events void onButtonShort(); // User button: short press void onButtonLong(); // User button: long press - void onExitShort(); // Exit button: short press - void onExitLong(); // Exit button: long press - void onNavUp(); // Navigate up - void onNavDown(); // Navigate down - void onNavLeft(); // Navigate left - void onNavRight(); // Navigate right + void applyingChanges(); + void onExitShort(); // Exit button: short press + void onExitLong(); // Exit button: long press + void onNavUp(); // Navigate up + void onNavDown(); // Navigate down + void onNavLeft(); // Navigate left + void onNavRight(); // Navigate right int beforeDeepSleep(void *unused); // Prepare for shutdown int beforeReboot(void *unused); // Prepare for reboot diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD/InkHUD.cpp index 9f05ae5bb..13b15b7e8 100644 --- a/src/graphics/niche/InkHUD/InkHUD.cpp +++ b/src/graphics/niche/InkHUD/InkHUD.cpp @@ -53,6 +53,13 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive, windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile); } +void InkHUD::InkHUD::notifyApplyingChanges() +{ + if (events) { + events->applyingChanges(); + } +} + // Start InkHUD! // Call this only after you have configured InkHUD void InkHUD::InkHUD::begin() diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD/InkHUD.h index 7325d8262..5280d9ac7 100644 --- a/src/graphics/niche/InkHUD/InkHUD.h +++ b/src/graphics/niche/InkHUD/InkHUD.h @@ -47,6 +47,7 @@ class InkHUD void setDriver(Drivers::EInk *driver); void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0); void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1); + void notifyApplyingChanges(); void begin(); @@ -76,6 +77,9 @@ class InkHUD void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default void toggleBatteryIcon(); + // Used by TipsApplet to force menu to start on Region selection + bool forceRegionMenu = false; + // Updating the display // - called by various InkHUD components diff --git a/src/graphics/niche/InkHUD/SystemApplet.h b/src/graphics/niche/InkHUD/SystemApplet.h index 7ee47eeb9..fb5b06e51 100644 --- a/src/graphics/niche/InkHUD/SystemApplet.h +++ b/src/graphics/niche/InkHUD/SystemApplet.h @@ -27,6 +27,7 @@ class SystemApplet : public Applet bool lockRequests = false; // - prevent other applets from triggering display updates virtual void onReboot() { onShutdown(); } // - handle reboot specially + virtual void onApplyingChanges() {} // Other system applets may take precedence over our own system applet though // The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank) From 91ad861b26edcd0382190b64ac49371d87860843 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Jan 2026 09:56:56 -0600 Subject: [PATCH 027/223] Add Thinknode M4 variant_shutdown() (#9449) --- .../nrf52840/ELECROW-ThinkNode-M4/variant.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp index af9bed998..5c4b6215b 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp @@ -49,3 +49,21 @@ void initVariant() pinMode(Battery_LED_4, OUTPUT); ledOff(Battery_LED_4); } + +/// called from main-nrf52.cpp during the cpuDeepSleep() function +void variant_shutdown() +{ + for (int pin = 0; pin < 48; pin++) { + if (pin == PIN_GPS_EN || pin == PIN_BUTTON1) { + continue; + } + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + if (pin >= 32) { + NRF_P1->DIRCLR = (1 << (pin - 32)); + } else { + NRF_GPIO->DIRCLR = (1 << pin); + } + } + digitalWrite(PIN_GPS_EN, HIGH); +} From b6a1020fc522fc07ec7c39b5325206f21bf84dc8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 27 Jan 2026 13:06:50 -0600 Subject: [PATCH 028/223] Add error handling for SPI command failures in LR11x0, RF95, and SX128x interfaces (#9447) --- src/mesh/LR11x0Interface.cpp | 13 ++++++++++++- src/mesh/RF95Interface.cpp | 3 +++ src/mesh/SX128xInterface.cpp | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 341afe78d..a8a2780ed 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -91,10 +91,21 @@ template bool LR11x0Interface::init() LOG_DEBUG("Set RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz"); #endif + // Allow extra time for TCXO to stabilize after power-on + delay(10); + int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); + + // Retry if we get SPI command failed - some units need extra TCXO stabilization time + if (res == RADIOLIB_ERR_SPI_CMD_FAILED) { + LOG_WARN("LR11x0 init failed with %d (SPI_CMD_FAILED), retrying after delay...", res); + delay(100); + res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage); + } + // \todo Display actual typename of the adapter, not just `LR11x0` LOG_INFO("LR11x0 init result %d", res); - if (res == RADIOLIB_ERR_CHIP_NOT_FOUND) + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) return false; LR11x0VersionInfo_t version; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 5588fc348..0c12401ca 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -177,6 +177,9 @@ bool RF95Interface::init() int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength); LOG_INFO("RF95 init result %d", res); + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) + return false; + LOG_INFO("Frequency set to %f", getFreq()); LOG_INFO("Bandwidth set to %f", bw); LOG_INFO("Power output set to %d", power); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index b4278c636..3ab63df3d 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -69,6 +69,8 @@ template bool SX128xInterface::init() int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength); // \todo Display actual typename of the adapter, not just `SX128x` LOG_INFO("SX128x init result %d", res); + if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) + return false; if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) { LOG_WARN("Radio only supports 2.4GHz LoRa. Adjusting Region and rebooting"); From d1edd386b65e297c1a96d78e9f06ecc3807db323 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:08:10 -0600 Subject: [PATCH 029/223] Update meshtastic/device-ui digest to 69739b8 (#9448) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index ee51d24ec..b7c46a6e1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -119,7 +119,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/37ad715b76cd6ca4aa500a4a4d9740e3cdf3e3cb.zip + https://github.com/meshtastic/device-ui/archive/69739b84f87a91568d3c421498bc89977937a141.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From cfda9bb8ef7026d8936d97799e5c53eeb6525613 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 13:12:03 -0600 Subject: [PATCH 030/223] Update protobufs (#9453) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 9 ++ src/mesh/generated/meshtastic/admin.pb.h | 111 +++++++++++++++++- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 16 ++- .../generated/meshtastic/telemetry.pb.cpp | 3 + src/mesh/generated/meshtastic/telemetry.pb.h | 41 +++++++ 7 files changed, 173 insertions(+), 11 deletions(-) diff --git a/protobufs b/protobufs index d9003b2b6..bc63a57f9 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit d9003b2b6c9f378066979ec83b013aca441cf240 +Subproject commit bc63a57f9e5dba8a7c90ee0bd4a9840862d61f6d diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index e358bc96d..01d3fa910 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -27,6 +27,15 @@ PB_BIND(meshtastic_SharedContact, meshtastic_SharedContact, AUTO) PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO) +PB_BIND(meshtastic_SensorConfig, meshtastic_SensorConfig, AUTO) + + +PB_BIND(meshtastic_SCD4X_config, meshtastic_SCD4X_config, AUTO) + + +PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO) + + diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 26b4343e9..f545ed9bf 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -77,7 +77,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11, /* TODO: REPLACE */ - meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12 + meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -169,6 +171,48 @@ typedef struct _meshtastic_KeyVerificationAdmin { uint32_t security_number; } meshtastic_KeyVerificationAdmin; +typedef struct _meshtastic_SCD4X_config { + /* Set Automatic self-calibration enabled */ + bool has_set_asc; + bool set_asc; + /* Recalibration target CO2 concentration in ppm (FRC or ASC) */ + bool has_set_target_co2_conc; + uint32_t set_target_co2_conc; + /* Reference temperature in degC */ + bool has_set_temperature; + float set_temperature; + /* Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure) */ + bool has_set_altitude; + uint32_t set_altitude; + /* Sensor ambient pressure in Pa. 70000 - 120000 Pa (overrides altitude) */ + bool has_set_ambient_pressure; + uint32_t set_ambient_pressure; + /* Perform a factory reset of the sensor */ + bool has_factory_reset; + bool factory_reset; + /* Power mode for sensor (true for low power, false for normal) */ + bool has_set_power_mode; + bool set_power_mode; +} meshtastic_SCD4X_config; + +typedef struct _meshtastic_SEN5X_config { + /* Reference temperature in degC */ + bool has_set_temperature; + float set_temperature; + /* One-shot mode (true for low power - one-shot mode, false for normal - continuous mode) */ + bool has_set_one_shot_mode; + bool set_one_shot_mode; +} meshtastic_SEN5X_config; + +typedef struct _meshtastic_SensorConfig { + /* SCD4X CO2 Sensor configuration */ + bool has_scd4x_config; + meshtastic_SCD4X_config scd4x_config; + /* SEN5X PM Sensor configuration */ + bool has_sen5x_config; + meshtastic_SEN5X_config sen5x_config; +} meshtastic_SensorConfig; + typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t; /* This message is handled by the Admin module and is responsible for all settings/channel read/write operations. This message is used to do settings operations to both remote AND local nodes. @@ -301,6 +345,8 @@ typedef struct _meshtastic_AdminMessage { bool nodedb_reset; /* Tell the node to reset into the OTA Loader */ meshtastic_AdminMessage_OTAEvent ota_request; + /* Parameters and sensor configuration */ + meshtastic_SensorConfig sensor_config; }; /* The node generates this key and sends it with any get_x_response packets. The client MUST include the same key with any set_x commands. Key expires after 300 seconds. @@ -323,8 +369,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD @@ -349,6 +395,9 @@ extern "C" { #define meshtastic_KeyVerificationAdmin_message_type_ENUMTYPE meshtastic_KeyVerificationAdmin_MessageType + + + /* Initializer values for message structs */ #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} @@ -357,6 +406,9 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} +#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default} +#define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_SEN5X_config_init_default {false, 0, false, 0} #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} @@ -364,6 +416,9 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} +#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero} +#define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_SEN5X_config_init_zero {false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_AdminMessage_InputEvent_event_code_tag 1 @@ -385,6 +440,17 @@ extern "C" { #define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2 #define meshtastic_KeyVerificationAdmin_nonce_tag 3 #define meshtastic_KeyVerificationAdmin_security_number_tag 4 +#define meshtastic_SCD4X_config_set_asc_tag 1 +#define meshtastic_SCD4X_config_set_target_co2_conc_tag 2 +#define meshtastic_SCD4X_config_set_temperature_tag 3 +#define meshtastic_SCD4X_config_set_altitude_tag 4 +#define meshtastic_SCD4X_config_set_ambient_pressure_tag 5 +#define meshtastic_SCD4X_config_factory_reset_tag 6 +#define meshtastic_SCD4X_config_set_power_mode_tag 7 +#define meshtastic_SEN5X_config_set_temperature_tag 1 +#define meshtastic_SEN5X_config_set_one_shot_mode_tag 2 +#define meshtastic_SensorConfig_scd4x_config_tag 1 +#define meshtastic_SensorConfig_sen5x_config_tag 2 #define meshtastic_AdminMessage_get_channel_request_tag 1 #define meshtastic_AdminMessage_get_channel_response_tag 2 #define meshtastic_AdminMessage_get_owner_request_tag 3 @@ -441,6 +507,7 @@ extern "C" { #define meshtastic_AdminMessage_factory_reset_config_tag 99 #define meshtastic_AdminMessage_nodedb_reset_tag 100 #define meshtastic_AdminMessage_ota_request_tag 102 +#define meshtastic_AdminMessage_sensor_config_tag 103 #define meshtastic_AdminMessage_session_passkey_tag 101 /* Struct field encoding specification for nanopb */ @@ -501,7 +568,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_se X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \ X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \ X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103) #define meshtastic_AdminMessage_CALLBACK NULL #define meshtastic_AdminMessage_DEFAULT NULL #define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel @@ -523,6 +591,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 10 #define meshtastic_AdminMessage_payload_variant_add_contact_MSGTYPE meshtastic_SharedContact #define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin #define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent +#define meshtastic_AdminMessage_payload_variant_sensor_config_MSGTYPE meshtastic_SensorConfig #define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, event_code, 1) \ @@ -569,6 +638,31 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4) #define meshtastic_KeyVerificationAdmin_CALLBACK NULL #define meshtastic_KeyVerificationAdmin_DEFAULT NULL +#define meshtastic_SensorConfig_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) +#define meshtastic_SensorConfig_CALLBACK NULL +#define meshtastic_SensorConfig_DEFAULT NULL +#define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config +#define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config + +#define meshtastic_SCD4X_config_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \ +X(a, STATIC, OPTIONAL, UINT32, set_target_co2_conc, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 3) \ +X(a, STATIC, OPTIONAL, UINT32, set_altitude, 4) \ +X(a, STATIC, OPTIONAL, UINT32, set_ambient_pressure, 5) \ +X(a, STATIC, OPTIONAL, BOOL, factory_reset, 6) \ +X(a, STATIC, OPTIONAL, BOOL, set_power_mode, 7) +#define meshtastic_SCD4X_config_CALLBACK NULL +#define meshtastic_SCD4X_config_DEFAULT NULL + +#define meshtastic_SEN5X_config_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 1) \ +X(a, STATIC, OPTIONAL, BOOL, set_one_shot_mode, 2) +#define meshtastic_SEN5X_config_CALLBACK NULL +#define meshtastic_SEN5X_config_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_AdminMessage_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg; @@ -576,6 +670,9 @@ extern const pb_msgdesc_t meshtastic_HamParameters_msg; extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg; extern const pb_msgdesc_t meshtastic_SharedContact_msg; extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; +extern const pb_msgdesc_t meshtastic_SensorConfig_msg; +extern const pb_msgdesc_t meshtastic_SCD4X_config_msg; +extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg @@ -585,6 +682,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; #define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg #define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg #define meshtastic_KeyVerificationAdmin_fields &meshtastic_KeyVerificationAdmin_msg +#define meshtastic_SensorConfig_fields &meshtastic_SensorConfig_msg +#define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg +#define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size @@ -594,6 +694,9 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 +#define meshtastic_SCD4X_config_size 29 +#define meshtastic_SEN5X_config_size 7 +#define meshtastic_SensorConfig_size 40 #define meshtastic_SharedContact_size 127 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 409805d24..57e7df8fc 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2279 +#define meshtastic_BackupPreferences_size 2362 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 2b44d0c9a..f11b13419 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -87,6 +87,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* Paxcounter Config */ bool has_paxcounter; meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* StatusMessage Config */ + bool has_statusmessage; + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } meshtastic_LocalModuleConfig; @@ -96,9 +99,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -124,6 +127,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_ambient_lighting_tag 12 #define meshtastic_LocalModuleConfig_detection_sensor_tag 13 #define meshtastic_LocalModuleConfig_paxcounter_tag 14 +#define meshtastic_LocalModuleConfig_statusmessage_tag 15 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -161,7 +165,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ -X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) +X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ +X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -177,6 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) #define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -186,9 +192,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 749 -#define meshtastic_LocalModuleConfig_size 675 +#define meshtastic_LocalModuleConfig_size 758 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index 345d7a157..fff75ebc1 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -33,6 +33,9 @@ PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, 2) PB_BIND(meshtastic_Nau7802Config, meshtastic_Nau7802Config, AUTO) +PB_BIND(meshtastic_SEN5XState, meshtastic_SEN5XState, AUTO) + + diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 131dd9949..dc9d876dc 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -435,6 +435,25 @@ typedef struct _meshtastic_Nau7802Config { float calibrationFactor; } meshtastic_Nau7802Config; +/* SEN5X State, for saving to flash */ +typedef struct _meshtastic_SEN5XState { + /* Last cleaning time for SEN5X */ + uint32_t last_cleaning_time; + /* Last cleaning time for SEN5X - valid flag */ + bool last_cleaning_valid; + /* Config flag for one-shot mode (see admin.proto) */ + bool one_shot_mode; + /* Last VOC state time for SEN55 */ + bool has_voc_state_time; + uint32_t voc_state_time; + /* Last VOC state validity flag for SEN55 */ + bool has_voc_state_valid; + bool voc_state_valid; + /* VOC state array (8x uint8t) for SEN55 */ + bool has_voc_state_array; + uint64_t voc_state_array; +} meshtastic_SEN5XState; + #ifdef __cplusplus extern "C" { @@ -455,6 +474,7 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} @@ -465,6 +485,7 @@ extern "C" { #define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} +#define meshtastic_SEN5XState_init_default {0, 0, 0, false, 0, false, 0, false, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} @@ -474,6 +495,7 @@ extern "C" { #define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} #define meshtastic_Nau7802Config_init_zero {0, 0} +#define meshtastic_SEN5XState_init_zero {0, 0, 0, false, 0, false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_DeviceMetrics_battery_level_tag 1 @@ -581,6 +603,12 @@ extern "C" { #define meshtastic_Telemetry_host_metrics_tag 8 #define meshtastic_Nau7802Config_zeroOffset_tag 1 #define meshtastic_Nau7802Config_calibrationFactor_tag 2 +#define meshtastic_SEN5XState_last_cleaning_time_tag 1 +#define meshtastic_SEN5XState_last_cleaning_valid_tag 2 +#define meshtastic_SEN5XState_one_shot_mode_tag 3 +#define meshtastic_SEN5XState_voc_state_time_tag 4 +#define meshtastic_SEN5XState_voc_state_valid_tag 5 +#define meshtastic_SEN5XState_voc_state_array_tag 6 /* Struct field encoding specification for nanopb */ #define meshtastic_DeviceMetrics_FIELDLIST(X, a) \ @@ -731,6 +759,16 @@ X(a, STATIC, SINGULAR, FLOAT, calibrationFactor, 2) #define meshtastic_Nau7802Config_CALLBACK NULL #define meshtastic_Nau7802Config_DEFAULT NULL +#define meshtastic_SEN5XState_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, last_cleaning_time, 1) \ +X(a, STATIC, SINGULAR, BOOL, last_cleaning_valid, 2) \ +X(a, STATIC, SINGULAR, BOOL, one_shot_mode, 3) \ +X(a, STATIC, OPTIONAL, UINT32, voc_state_time, 4) \ +X(a, STATIC, OPTIONAL, BOOL, voc_state_valid, 5) \ +X(a, STATIC, OPTIONAL, FIXED64, voc_state_array, 6) +#define meshtastic_SEN5XState_CALLBACK NULL +#define meshtastic_SEN5XState_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg; extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg; extern const pb_msgdesc_t meshtastic_PowerMetrics_msg; @@ -740,6 +778,7 @@ extern const pb_msgdesc_t meshtastic_HealthMetrics_msg; extern const pb_msgdesc_t meshtastic_HostMetrics_msg; extern const pb_msgdesc_t meshtastic_Telemetry_msg; extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; +extern const pb_msgdesc_t meshtastic_SEN5XState_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_DeviceMetrics_fields &meshtastic_DeviceMetrics_msg @@ -751,6 +790,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_HostMetrics_fields &meshtastic_HostMetrics_msg #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg #define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg +#define meshtastic_SEN5XState_fields &meshtastic_SEN5XState_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size @@ -762,6 +802,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; #define meshtastic_LocalStats_size 87 #define meshtastic_Nau7802Config_size 16 #define meshtastic_PowerMetrics_size 81 +#define meshtastic_SEN5XState_size 27 #define meshtastic_Telemetry_size 272 #ifdef __cplusplus From 10b2eae70c7f36895892bbbd3094f2cb4544026d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Jan 2026 13:56:32 -0600 Subject: [PATCH 031/223] Move more code out of main-nrf52 into variant.cpp (#9450) --- src/platform/nrf52/main-nrf52.cpp | 16 ---------------- .../nrf52840/ELECROW-ThinkNode-M1/variant.cpp | 7 +++++++ .../diy/nrf52_promicro_diy_tcxo/variant.cpp | 7 +++++++ 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 11b05165c..0376a1dad 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -464,22 +464,6 @@ void cpuDeepSleep(uint32_t msecToWake) // FIXME, use non-init RAM per // https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled -#ifdef ELECROW_ThinkNode_M1 - nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input - nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; - nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense); - - nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP); - nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; - nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1); -#endif - -#ifdef PROMICRO_DIY_TCXO - nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin - nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge - nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep -#endif - #ifdef BATTERY_LPCOMP_INPUT // Wake up if power rises again nrf_lpcomp_config_t c; diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp index 2ce84bc57..1560cde73 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp @@ -59,4 +59,11 @@ void variant_shutdown() NRF_GPIO->DIRCLR = (1 << pin); } } + nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense); + + nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP); + nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1); } \ No newline at end of file diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp index 5869ed1d4..384c618e2 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.cpp @@ -36,3 +36,10 @@ void initVariant() pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); } + +void variant_shutdown() +{ + nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin + nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge + nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep +} From 23a8b5a66f9ad9f5176bd768beef2200c2f3066c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=9E=97?= <47050377+linser233@users.noreply.github.com> Date: Wed, 28 Jan 2026 04:50:50 +0800 Subject: [PATCH 032/223] Fix uMesh RF POWER configuration error (#9326) * fix issue https://github.com/linser233/uMesh/issues/1 * fix issue https://github.com/linser233/uMesh/issues/1 * Update and rename lora-usb-umesh-1262.yaml to lora-usb-umesh-1262-30dbm.yaml * Update and rename lora-usb-umesh-1268.yaml to lora-usb-umesh-1268-30dbm.yaml --- bin/config.d/lora-usb-umesh-1262-30dbm.yaml | 23 +++++++++++++++++++++ bin/config.d/lora-usb-umesh-1262.yaml | 15 -------------- bin/config.d/lora-usb-umesh-1268-30dbm.yaml | 23 +++++++++++++++++++++ bin/config.d/lora-usb-umesh-1268.yaml | 15 -------------- 4 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 bin/config.d/lora-usb-umesh-1262-30dbm.yaml delete mode 100644 bin/config.d/lora-usb-umesh-1262.yaml create mode 100644 bin/config.d/lora-usb-umesh-1268-30dbm.yaml delete mode 100644 bin/config.d/lora-usb-umesh-1268.yaml diff --git a/bin/config.d/lora-usb-umesh-1262-30dbm.yaml b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml new file mode 100644 index 000000000..7726eccd1 --- /dev/null +++ b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml @@ -0,0 +1,23 @@ +Lora: + Module: sx1262 + CS: 0 + IRQ: 6 + Reset: 1 + Busy: 4 + RXen: 2 + DIO2_AS_RF_SWITCH: true + spidev: ch341 + USB_PID: 0x5512 + USB_VID: 0x1A86 + DIO3_TCXO_VOLTAGE: true +# USB_Serialnum: 12345678 + SX126X_MAX_POWER: 22 +# Reduce output power to improve EMI + NUM_PA_POINTS: 22 + TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 +# Note: This module integrates an additional PA to achieve higher output power. +# The 'power' parameter here does not represent the actual RF output. +# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). +# Each array element corresponds to the additional gain when that input level is set, +# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index]. +# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information. diff --git a/bin/config.d/lora-usb-umesh-1262.yaml b/bin/config.d/lora-usb-umesh-1262.yaml deleted file mode 100644 index 6008e63b7..000000000 --- a/bin/config.d/lora-usb-umesh-1262.yaml +++ /dev/null @@ -1,15 +0,0 @@ -Lora: - Module: sx1262 - CS: 0 - IRQ: 6 - Reset: 1 - Busy: 4 - RXen: 2 - DIO2_AS_RF_SWITCH: true - spidev: ch341 - USB_PID: 0x5512 - USB_VID: 0x1A86 - DIO3_TCXO_VOLTAGE: true -# USB_Serialnum: 12345678 - SX126X_MAX_POWER: 30 -# Reduce output power to improve EMI diff --git a/bin/config.d/lora-usb-umesh-1268-30dbm.yaml b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml new file mode 100644 index 000000000..c054a92f9 --- /dev/null +++ b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml @@ -0,0 +1,23 @@ +Lora: + Module: sx1268 + CS: 0 + IRQ: 6 + Reset: 1 + Busy: 4 + RXen: 2 + DIO2_AS_RF_SWITCH: true + spidev: ch341 + USB_PID: 0x5512 + USB_VID: 0x1A86 + DIO3_TCXO_VOLTAGE: true +# USB_Serialnum: 12345678 + SX126X_MAX_POWER: 22 +# Reduce output power to improve EMI + NUM_PA_POINTS: 22 + TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 +# Note: This module integrates an additional PA to achieve higher output power. +# The 'power' parameter here does not represent the actual RF output. +# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). +# Each array element corresponds to the additional gain when that input level is set, +# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index]. +# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information. diff --git a/bin/config.d/lora-usb-umesh-1268.yaml b/bin/config.d/lora-usb-umesh-1268.yaml deleted file mode 100644 index 637472966..000000000 --- a/bin/config.d/lora-usb-umesh-1268.yaml +++ /dev/null @@ -1,15 +0,0 @@ -Lora: - Module: sx1268 - CS: 0 - IRQ: 6 - Reset: 1 - Busy: 4 - RXen: 2 - DIO2_AS_RF_SWITCH: true - spidev: ch341 - USB_PID: 0x5512 - USB_VID: 0x1A86 - DIO3_TCXO_VOLTAGE: true -# USB_Serialnum: 12345678 - SX126X_MAX_POWER: 30 -# Reduce output power to improve EMI From fd498bebad72f6e69b2bf65ea6b9057f62667a48 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Jan 2026 16:09:18 -0600 Subject: [PATCH 033/223] Add support for Hackaday Communicator function keys (#9444) --- src/graphics/Screen.cpp | 20 ++++++++++++++++++++ src/input/HackadayCommunicatorKeyboard.cpp | 13 +++++++------ src/input/InputBroker.h | 5 +++++ src/input/TCA8418KeyboardBase.h | 7 ++++++- src/input/kbI2cBase.cpp | 20 ++++++++++++++++++++ 5 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 8bf69b7a0..111a47f7c 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1731,6 +1731,26 @@ int Screen::handleInputEvent(const InputEvent *event) showFrame(FrameDirection::PREVIOUS); } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) { showFrame(FrameDirection::NEXT); + } else if (event->inputEvent == INPUT_BROKER_FN_F1) { + this->ui->switchToFrame(0); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F2) { + this->ui->switchToFrame(1); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F3) { + this->ui->switchToFrame(2); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F4) { + this->ui->switchToFrame(3); + lastScreenTransition = millis(); + setFastFramerate(); + } else if (event->inputEvent == INPUT_BROKER_FN_F5) { + this->ui->switchToFrame(4); + lastScreenTransition = millis(); + setFastFramerate(); } else if (event->inputEvent == INPUT_BROKER_UP_LONG) { // Long press up button for fast frame switching showPrevFrame(); diff --git a/src/input/HackadayCommunicatorKeyboard.cpp b/src/input/HackadayCommunicatorKeyboard.cpp index 87c8a24ae..c6a9e0ae8 100644 --- a/src/input/HackadayCommunicatorKeyboard.cpp +++ b/src/input/HackadayCommunicatorKeyboard.cpp @@ -20,20 +20,20 @@ constexpr uint8_t modifierLeftShift = 0b0001; // Num chars per key, Modulus for rotating through characters static uint8_t HackadayCommunicatorTapMod[_TCA8418_NUM_KEYS] = { - 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 2, 2, 2, 1, 2, 2, 0, 0, 0, 2, 1, 2, 2, 0, 1, 1, 0, }; static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{}, - {}, + {Key::FUNCTION_F1}, {'+'}, {'9'}, {'8'}, {'7'}, - {'2'}, - {'3'}, - {'4'}, - {'5'}, + {Key::FUNCTION_F2}, + {Key::FUNCTION_F3}, + {Key::FUNCTION_F4}, + {Key::FUNCTION_F5}, {Key::ESC}, {'q', 'Q'}, {'w', 'W'}, @@ -141,6 +141,7 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key) if (state == Init || state == Busy) { return; } + LOG_DEBUG("Key pressed: %u", key); if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { modifierFlag = 0; diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index c55d7fa53..970e9969a 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -27,6 +27,11 @@ enum input_broker_event { INPUT_BROKER_SHUTDOWN = 0x9b, INPUT_BROKER_GPS_TOGGLE = 0x9e, INPUT_BROKER_SEND_PING = 0xaf, + INPUT_BROKER_FN_F1 = 0xf1, + INPUT_BROKER_FN_F2 = 0xf2, + INPUT_BROKER_FN_F3 = 0xf3, + INPUT_BROKER_FN_F4 = 0xf4, + INPUT_BROKER_FN_F5 = 0xf5, INPUT_BROKER_MATRIXKEY = 0xFE, INPUT_BROKER_ANYKEY = 0xff diff --git a/src/input/TCA8418KeyboardBase.h b/src/input/TCA8418KeyboardBase.h index 8e509ac7e..e608c6da5 100644 --- a/src/input/TCA8418KeyboardBase.h +++ b/src/input/TCA8418KeyboardBase.h @@ -26,7 +26,12 @@ class TCA8418KeyboardBase GPS_TOGGLE = 0x9E, MUTE_TOGGLE = 0xAC, SEND_PING = 0xAF, - BL_TOGGLE = 0xAB + BL_TOGGLE = 0xAB, + FUNCTION_F1 = 0xF1, + FUNCTION_F2 = 0xF2, + FUNCTION_F3 = 0xF3, + FUNCTION_F4 = 0xF4, + FUNCTION_F5 = 0xF5 }; typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len); diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 12d0822f6..d744ee2ca 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -321,6 +321,26 @@ int32_t KbI2cBase::runOnce() e.inputEvent = INPUT_BROKER_ANYKEY; e.kbchar = INPUT_BROKER_MSG_TAB; break; + case TCA8418KeyboardBase::FUNCTION_F1: + e.inputEvent = INPUT_BROKER_FN_F1; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F2: + e.inputEvent = INPUT_BROKER_FN_F2; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F3: + e.inputEvent = INPUT_BROKER_FN_F3; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F4: + e.inputEvent = INPUT_BROKER_FN_F4; + e.kbchar = 0x00; + break; + case TCA8418KeyboardBase::FUNCTION_F5: + e.inputEvent = INPUT_BROKER_FN_F5; + e.kbchar = 0x00; + break; default: if (nextEvent > 127) { e.inputEvent = INPUT_BROKER_NONE; From 69a42e1fd2a65e34ca87cd2e9c9f8c3d89ccf627 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 27 Jan 2026 18:00:20 -0600 Subject: [PATCH 034/223] Add portduino_status, assign hardware device IDs... (#9441) * Add portduino_status, assign hardware device IDs, and try to recover a CH341 device on a USB error * Minor fixes suggested by Copilot --- src/main.cpp | 39 ++++++++++++++++- src/mesh/NodeDB.cpp | 7 ++- src/mesh/SX126xInterface.cpp | 16 ++++++- src/platform/portduino/PortduinoGlue.cpp | 11 +++++ src/platform/portduino/PortduinoGlue.h | 9 +++- src/platform/portduino/USBHal.h | 54 ++++++++++++++++++++++-- src/platform/portduino/architecture.h | 2 +- variants/native/portduino.ini | 2 +- 8 files changed, 129 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 93cc89296..3ccc06afb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1401,7 +1401,43 @@ void loop() if (inputBroker) inputBroker->processInputEventQueue(); #endif -#if ARCH_PORTDUINO && HAS_TFT +#if ARCH_PORTDUINO + if (portduino_config.lora_spi_dev == "ch341" && ch341Hal != nullptr) { + ch341Hal->checkError(); + } + if (portduino_status.LoRa_in_error && rebootAtMsec == 0) { + LOG_ERROR("LoRa in error detected, attempting to recover"); + if (rIf != nullptr) { + delete rIf; + rIf = nullptr; + } + if (portduino_config.lora_spi_dev == "ch341") { + if (ch341Hal != nullptr) { + delete ch341Hal; + ch341Hal = nullptr; + sleep(3); + } + try { + ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid, + portduino_config.lora_usb_pid); + } catch (std::exception &e) { + std::cerr << e.what() << std::endl; + std::cerr << "Could not initialize CH341 device!" << std::endl; + exit(EXIT_FAILURE); + } + } + if (initLoRa()) { + router->addInterface(rIf); + portduino_status.LoRa_in_error = false; + } else { + LOG_WARN("Reconfigure failed, rebooting"); + if (screen) { + screen->showSimpleBanner("Rebooting..."); + } + rebootAtMsec = millis() + 25; + } + } +#if HAS_TFT if (screen && portduino_config.displayPanel == x11 && config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { auto dispdev = screen->getDisplayDevice(); @@ -1409,6 +1445,7 @@ void loop() static_cast(dispdev)->sdlLoop(); } #endif +#endif #if HAS_SCREEN && ENABLE_MESSAGE_PERSISTENCE messageStoreAutosaveTick(); #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index ca6d49ca2..30d1633d5 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -2247,7 +2247,10 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co // Currently portuino is mostly used for simulation. Make sure the user notices something really bad happened #ifdef ARCH_PORTDUINO - LOG_ERROR("A critical failure occurred, portduino is exiting"); - exit(2); + LOG_ERROR("A critical failure occurred"); + // TODO: Determine if other critical errors should also cause an immediate exit + if (code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_RECOVERABLE || + code == meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE) + exit(2); #endif } diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 498496a3b..9dfc46bee 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -269,8 +269,12 @@ template void SX126xInterface::setStandby() if (err != RADIOLIB_ERR_NONE) LOG_DEBUG("SX126x standby %s%d", radioLibErr, err); +#ifdef ARCH_PORTDUINO + if (err != RADIOLIB_ERR_NONE) + portduino_status.LoRa_in_error = true; +#else assert(err == RADIOLIB_ERR_NONE); - +#endif isReceiving = false; // If we were receiving, not any more activeReceiveStart = 0; disableInterrupt(); @@ -313,7 +317,12 @@ template void SX126xInterface::startReceive() int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS); if (err != RADIOLIB_ERR_NONE) LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d", radioLibErr, err); +#ifdef ARCH_PORTDUINO + if (err != RADIOLIB_ERR_NONE) + portduino_status.LoRa_in_error = true; +#else assert(err == RADIOLIB_ERR_NONE); +#endif RadioLibInterface::startReceive(); @@ -341,7 +350,12 @@ template bool SX126xInterface::isChannelActive() return true; if (result != RADIOLIB_CHANNEL_FREE) LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result); +#ifdef ARCH_PORTDUINO + if (result == RADIOLIB_ERR_WRONG_MODEM) + portduino_status.LoRa_in_error = true; +#else assert(result != RADIOLIB_ERR_WRONG_MODEM); +#endif return false; } diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 9159d5954..d0d8ba40f 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,7 @@ #include "platform/portduino/USBHal.h" portduino_config_struct portduino_config; +portduino_status_struct portduino_status; std::ofstream traceFile; std::ofstream JSONFile; Ch341Hal *ch341Hal = nullptr; @@ -400,6 +402,11 @@ void portduinoSetup() if (found_hat) { product_config = cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml"); + if (strncmp(hat_vendor, "RAK", strlen("RAK")) == 0 && + strncmp(autoconf_product, "6421 Pi Hat", strlen("6421 Pi Hat")) == 0) { + std::cout << "autoconf: Setting hardwareModel to RAK6421" << std::endl; + portduino_status.hardwareModel = meshtastic_HardwareModel_RAK6421; + } } else if (found_ch341) { product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml"); // look for more data after the null terminator @@ -408,6 +415,10 @@ void portduinoSetup() memcpy(portduino_config.device_id, autoconf_product + len + 1, 16); if (!memfll(portduino_config.device_id, '\0', 16) && !memfll(portduino_config.device_id, 0xff, 16)) { portduino_config.has_device_id = true; + if (strncmp(autoconf_product, "MESHSTICK 1262", strlen("MESHSTICK 1262")) == 0) { + std::cout << "autoconf: Setting hardwareModel to Meshstick 1262" << std::endl; + portduino_status.hardwareModel = meshtastic_HardwareModel_MESHSTICK_1262; + } } } } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index eb4eb8925..af511be6e 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -1,15 +1,22 @@ #pragma once #include #include +#include #include #include #include "LR11x0Interface.h" #include "Module.h" #include "mesh/generated/meshtastic/mesh.pb.h" -#include "platform/portduino/USBHal.h" #include "yaml-cpp/yaml.h" +extern struct portduino_status_struct { + bool LoRa_in_error = false; + _meshtastic_HardwareModel hardwareModel = meshtastic_HardwareModel_PORTDUINO; +} portduino_status; + +#include "platform/portduino/USBHal.h" + // Product strings for auto-configuration // {"PRODUCT_STRING", "CONFIG.YAML"} // YAML paths are relative to `meshtastic/available.d` diff --git a/src/platform/portduino/USBHal.h b/src/platform/portduino/USBHal.h index ecc292430..441f75b10 100644 --- a/src/platform/portduino/USBHal.h +++ b/src/platform/portduino/USBHal.h @@ -9,6 +9,8 @@ #include #include +extern uint32_t rebootAtMsec; + // include the library for Raspberry GPIO pins #define PI_RISING (PINEDIO_INT_MODE_RISING) @@ -45,7 +47,7 @@ class Ch341Hal : public RadioLibHal int32_t ret = pinedio_init(&pinedio, NULL); if (ret != 0) { std::string s = "Could not open SPI: "; - throw(s + std::to_string(ret)); + throw std::runtime_error(s + std::to_string(ret)); } pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0); @@ -74,30 +76,55 @@ class Ch341Hal : public RadioLibHal // RADIOLIB_NC as an alias for non-connected pins void pinMode(uint32_t pin, uint32_t mode) override { + if (checkError()) { + return; + } if (pin == RADIOLIB_NC) { return; } - pinedio_set_pin_mode(&pinedio, pin, mode); + auto res = pinedio_set_pin_mode(&pinedio, pin, mode); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal pinMode: Could not set pin %u mode to %u: %d", pin, mode, res); + } } void digitalWrite(uint32_t pin, uint32_t value) override { + if (checkError()) { + return; + } if (pin == RADIOLIB_NC) { return; } - pinedio_digital_write(&pinedio, pin, value); + auto res = pinedio_digital_write(&pinedio, pin, value); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal digitalWrite: Could not write pin %u: %d", pin, res); + portduino_status.LoRa_in_error = true; + } } uint32_t digitalRead(uint32_t pin) override { + if (checkError()) { + return 0; + } if (pin == RADIOLIB_NC) { return 0; } - return pinedio_digital_read(&pinedio, pin); + auto res = pinedio_digital_read(&pinedio, pin); + if (res < 0 && rebootAtMsec == 0) { + LOG_ERROR("USBHal digitalRead: Could not read pin %u: %d", pin, res); + portduino_status.LoRa_in_error = true; + return 0; + } + return res; } void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override { + if (checkError()) { + return; + } if (interruptNum == RADIOLIB_NC) { return; } @@ -107,6 +134,9 @@ class Ch341Hal : public RadioLibHal void detachInterrupt(uint32_t interruptNum) override { + if (checkError()) { + return; + } if (interruptNum == RADIOLIB_NC) { return; } @@ -152,6 +182,9 @@ class Ch341Hal : public RadioLibHal void spiTransfer(uint8_t *out, size_t len, uint8_t *in) { + if (checkError()) { + return; + } int32_t ret = pinedio_transceive(&this->pinedio, out, in, len); if (ret < 0) { std::cerr << "Could not perform SPI transfer: " << ret << std::endl; @@ -160,9 +193,22 @@ class Ch341Hal : public RadioLibHal void spiEndTransaction() {} void spiEnd() {} + bool checkError() + { + if (pinedio.in_error) { + if (!has_warned) + LOG_ERROR("USBHal: libch341 in_error detected"); + portduino_status.LoRa_in_error = true; + has_warned = true; + return true; + } + has_warned = false; + return false; + } private: pinedio_inst pinedio = {0}; + bool has_warned = false; }; #endif diff --git a/src/platform/portduino/architecture.h b/src/platform/portduino/architecture.h index e10519d21..9ee8ad366 100644 --- a/src/platform/portduino/architecture.h +++ b/src/platform/portduino/architecture.h @@ -6,7 +6,7 @@ // set HW_VENDOR // -#define HW_VENDOR meshtastic_HardwareModel_PORTDUINO +#define HW_VENDOR portduino_status.hardwareModel #ifndef HAS_BUTTON #define HAS_BUTTON 1 diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index cc6c39aa2..55905481a 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -29,7 +29,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.7 # 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/af9bc27c9c30fa90772279925b7c5913dff789b4.zip + 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 adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main From 4eb4c4b584f529fd55c796bbb6aae9d2377fd470 Mon Sep 17 00:00:00 2001 From: Jason P Date: Tue, 27 Jan 2026 20:11:56 -0600 Subject: [PATCH 035/223] BaseUI Message Bubble Improvements (#9452) * Improve Message bubbles for more distinct markers and improved layout * Tune message bubble size and corner markers * Finish message bubble tuning --------- Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- src/graphics/draw/MessageRenderer.cpp | 83 +++++++++------------------ 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 72746e415..193164439 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -431,45 +431,6 @@ static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool i return iconTop + tallest - 1; } -static void drawRoundedRectOutline(OLEDDisplay *display, int x, int y, int w, int h, int r) -{ - if (w <= 1 || h <= 1) - return; - - if (r < 0) - r = 0; - - int maxR = (std::min(w, h) / 2) - 1; - if (r > maxR) - r = maxR; - - if (r == 0) { - display->drawRect(x, y, w, h); - return; - } - - const int x0 = x; - const int y0 = y; - const int x1 = x + w - 1; - const int y1 = y + h - 1; - - // sides - if (x0 + r <= x1 - r) { - display->drawLine(x0 + r, y0, x1 - r, y0); // top - display->drawLine(x0 + r, y1, x1 - r, y1); // bottom - } - if (y0 + r <= y1 - r) { - display->drawLine(x0, y0 + r, x0, y1 - r); // left - display->drawLine(x1, y0 + r, x1, y1 - r); // right - } - - // corner arcs - display->drawCircleQuads(x0 + r, y0 + r, r, 2); // top left - display->drawCircleQuads(x1 - r, y0 + r, r, 1); // top right - display->drawCircleQuads(x1 - r, y1 - r, r, 8); // bottom right - display->drawCircleQuads(x0 + r, y1 - r, r, 4); // bottom left -} - static std::vector buildMessageBlocks(const std::vector &isHeaderVec, const std::vector &isMineVec) { std::vector blocks; @@ -909,27 +870,37 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 bubbleW = std::max(1, rightEdge - bubbleX); if (bubbleW > 1 && bubbleH > 1) { - int r = BUBBLE_RADIUS; - int maxR = (std::min(bubbleW, bubbleH) / 2) - 1; - if (maxR < 0) - maxR = 0; - if (r > maxR) - r = maxR; - - drawRoundedRectOutline(display, bubbleX, topY, bubbleW, bubbleH, r); - const int extra = 3; - const int rr = r + extra; int x1 = bubbleX + bubbleW - 1; int y1 = topY + bubbleH - 1; - if (!b.mine) { - // top-left corner square - display->drawLine(bubbleX, topY, bubbleX + rr, topY); - display->drawLine(bubbleX, topY, bubbleX, topY + rr); + if (b.mine) { + // Send Message (Right side) + display->drawRect(x1 + 2 - bubbleW, y1 - bubbleH, bubbleW, bubbleH); + // Top Right Corner + display->drawRect(x1, topY, 2, 1); + display->drawRect(x1, topY, 1, 2); + // Bottom Right Corner + display->drawRect(x1 - 1, bottomY - 2, 2, 1); + display->drawRect(x1, bottomY - 3, 1, 2); + // Knock the corners off to make a bubble + display->setColor(BLACK); + display->drawRect(x1 - bubbleW, topY - 1, 1, 1); + display->drawRect(x1 - bubbleW, bottomY - 1, 1, 1); + display->setColor(WHITE); } else { - // bottom-right corner square - display->drawLine(x1 - rr, y1, x1, y1); - display->drawLine(x1, y1 - rr, x1, y1); + // Received Message (Left Side) + display->drawRect(bubbleX, topY, bubbleW + 1, bubbleH); + // Top Left Corner + display->drawRect(bubbleX + 1, topY + 1, 2, 1); + display->drawRect(bubbleX + 1, topY + 1, 1, 2); + // Bottom Left Corner + display->drawRect(bubbleX + 1, bottomY - 1, 2, 1); + display->drawRect(bubbleX + 1, bottomY - 2, 1, 2); + // Knock the corners off to make a bubble + display->setColor(BLACK); + display->drawRect(bubbleX + bubbleW, topY, 1, 1); + display->drawRect(bubbleX + bubbleW, bottomY, 1, 1); + display->setColor(WHITE); } } } From d0562e1ee657dec366751a10ff8b6955b2b12d13 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Jan 2026 10:50:56 -0600 Subject: [PATCH 036/223] Add model workflows (#9462) * Add GitHub workflows for issue completeness, duplicate detection, onboarding, and contribution quality checks * Fix indentation * Refactor GitHub workflows for issue handling * Consolidate to two triage workflows * Update .github/workflows/models_pr_triage.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/models_issue_triage.yml | 204 ++++++++++++++++++++++ .github/workflows/models_pr_triage.yml | 138 +++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 .github/workflows/models_issue_triage.yml create mode 100644 .github/workflows/models_pr_triage.yml diff --git a/.github/workflows/models_issue_triage.yml b/.github/workflows/models_issue_triage.yml new file mode 100644 index 000000000..f61a15fe6 --- /dev/null +++ b/.github/workflows/models_issue_triage.yml @@ -0,0 +1,204 @@ +name: Issue Triage (Models) + +on: + issues: + types: [opened] + +permissions: + issues: write + models: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number }} + cancel-in-progress: true + +jobs: + triage: + if: ${{ github.repository == 'meshtastic/firmware' && github.event.issue.user.type != 'Bot' }} + runs-on: ubuntu-latest + steps: + # ───────────────────────────────────────────────────────────────────────── + # Step 1: Quality check (spam/AI-slop detection) - runs first, exits early if spam + # ───────────────────────────────────────────────────────────────────────── + - name: Detect spam or low-quality content + uses: actions/ai-inference@v2 + id: quality + continue-on-error: true + with: + max-tokens: 20 + prompt: | + Is this GitHub issue spam, AI-generated slop, or low quality? + + Title: ${{ github.event.issue.title }} + Body: ${{ github.event.issue.body }} + + Respond with exactly one of: spam, ai-generated, needs-review, ok + system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop. + model: openai/gpt-4o-mini + + - name: Apply quality label if needed + if: steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok' + uses: actions/github-script@v8 + env: + QUALITY_LABEL: ${{ steps.quality.outputs.response }} + with: + script: | + const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'spam': { color: 'd73a4a', description: 'Possible spam' }, + 'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' }, + 'needs-review': { color: 'f9d0c4', description: 'Needs human review' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] }); + + // Set output to skip remaining steps + core.setOutput('is_spam', 'true'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 2: Duplicate detection - only if not spam + # ───────────────────────────────────────────────────────────────────────── + - name: Detect duplicate issues + if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '' + uses: pelikhan/action-genai-issue-dedup@bdb3b5d9451c1090ffcdf123d7447a5e7c7a2528 # v0.0.19 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + # ───────────────────────────────────────────────────────────────────────── + # Step 3: Completeness check + auto-labeling (combined into one AI call) + # ───────────────────────────────────────────────────────────────────────── + - name: Determine if completeness check should be skipped + if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '' + uses: actions/github-script@v8 + id: check-skip + with: + script: | + const title = (context.payload.issue.title || '').toLowerCase(); + const labels = (context.payload.issue.labels || []).map(label => label.name); + const hasFeatureRequest = title.includes('feature request'); + const hasEnhancement = labels.includes('enhancement'); + const shouldSkip = hasFeatureRequest && hasEnhancement; + core.setOutput('should_skip', shouldSkip ? 'true' : 'false'); + + - name: Analyze issue completeness and determine labels + if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' + uses: actions/ai-inference@v2 + id: analysis + continue-on-error: true + with: + prompt: | + Analyze this GitHub issue for completeness and determine if it needs labels. + + If this looks like a bug on the device/firmware (crash, reboot, lockup, radio issues, GPS issues, display issues, power/sleep issues), request device logs and explain how to get them: + + Web Flasher logs: + - Go to https://flasher.meshtastic.org + - Connect the device via USB and click Connect + - Open the device console/log output, reproduce the problem, then copy/download and attach/paste the logs + + Meshtastic CLI logs: + - Run: meshtastic --port --noproto + - Reproduce the problem, then copy/paste the terminal output + + Also request key context if missing: device model/variant, firmware version, region, steps to reproduce, expected vs actual. + + Respond ONLY with JSON: + { + "complete": true|false, + "comment": "Your helpful comment requesting missing info, or empty string if complete", + "label": "needs-logs" | "needs-info" | "none" + } + + Use "needs-logs" if this is a device bug AND no logs are attached. + Use "needs-info" if basic info like firmware version or steps to reproduce are missing. + Use "none" if the issue is complete or is a feature request. + + Title: ${{ github.event.issue.title }} + Body: ${{ github.event.issue.body }} + system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels. + model: openai/gpt-4o-mini + + - name: Process analysis result + if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' && steps.analysis.outputs.response != '' + uses: actions/github-script@v8 + id: process + env: + AI_RESPONSE: ${{ steps.analysis.outputs.response }} + with: + script: | + const raw = (process.env.AI_RESPONSE || '').trim(); + + let complete = false; + let comment = ''; + let label = 'none'; + + try { + const parsed = JSON.parse(raw); + complete = !!parsed.complete; + comment = (parsed.comment ?? '').toString().trim(); + label = (parsed.label ?? 'none').toString().trim().toLowerCase(); + } catch { + // If JSON parse fails, treat as incomplete with raw response as comment + complete = false; + comment = raw; + label = 'none'; + } + + // Validate label + const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']); + if (!allowedLabels.has(label)) label = 'none'; + + core.setOutput('should_comment', (!complete && comment.length > 0) ? 'true' : 'false'); + core.setOutput('comment_body', comment); + core.setOutput('label', label); + + - name: Apply triage label + if: steps.process.outputs.label != '' && steps.process.outputs.label != 'none' + uses: actions/github-script@v8 + env: + LABEL_NAME: ${{ steps.process.outputs.label }} + with: + script: | + const label = process.env.LABEL_NAME; + const labelMeta = { + 'needs-logs': { color: 'cfd3d7', description: 'Device logs requested for triage' }, + 'needs-info': { color: 'f9d0c4', description: 'More information requested for triage' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] }); + + - name: Comment on issue + if: steps.process.outputs.should_comment == 'true' + uses: actions/github-script@v8 + env: + COMMENT_BODY: ${{ steps.process.outputs.comment_body }} + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: process.env.COMMENT_BODY + }); diff --git a/.github/workflows/models_pr_triage.yml b/.github/workflows/models_pr_triage.yml new file mode 100644 index 000000000..ef303c02a --- /dev/null +++ b/.github/workflows/models_pr_triage.yml @@ -0,0 +1,138 @@ +name: PR Triage (Models) + +on: + pull_request_target: + types: [opened] + +permissions: + pull-requests: write + issues: write + models: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + triage: + if: ${{ github.repository == 'meshtastic/firmware' && github.event.pull_request.user.type != 'Bot' }} + runs-on: ubuntu-latest + steps: + # ───────────────────────────────────────────────────────────────────────── + # Step 1: Check if PR already has automation/type labels (skip if so) + # ───────────────────────────────────────────────────────────────────────── + - name: Check existing labels + uses: actions/github-script@v8 + id: check-labels + with: + script: | + const skipLabels = new Set(['automation']); + const typeLabels = new Set(['bugfix', 'hardware-support', 'enhancement', 'dependencies', 'submodules', 'github_actions', 'trunk', 'cleanup']); + const prLabels = context.payload.pull_request.labels.map(l => l.name); + + const shouldSkipAll = prLabels.some(l => skipLabels.has(l)); + const hasTypeLabel = prLabels.some(l => typeLabels.has(l)); + + core.setOutput('skip_all', shouldSkipAll ? 'true' : 'false'); + core.setOutput('has_type_label', hasTypeLabel ? 'true' : 'false'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 2: Quality check (spam/AI-slop detection) + # ───────────────────────────────────────────────────────────────────────── + - name: Detect spam or low-quality content + if: steps.check-labels.outputs.skip_all != 'true' + uses: actions/ai-inference@v2 + id: quality + continue-on-error: true + with: + max-tokens: 20 + prompt: | + Is this GitHub pull request spam, AI-generated slop, or low quality? + + Title: ${{ github.event.pull_request.title }} + Body: ${{ github.event.pull_request.body }} + + Respond with exactly one of: spam, ai-generated, needs-review, ok + system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop. + model: openai/gpt-4o-mini + + - name: Apply quality label if needed + if: steps.check-labels.outputs.skip_all != 'true' && steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok' + uses: actions/github-script@v8 + id: quality-label + env: + QUALITY_LABEL: ${{ steps.quality.outputs.response }} + with: + script: | + const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'spam': { color: 'd73a4a', description: 'Possible spam' }, + 'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' }, + 'needs-review': { color: 'f9d0c4', description: 'Needs human review' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] }); + + core.setOutput('is_spam', 'true'); + + # ───────────────────────────────────────────────────────────────────────── + # Step 3: Auto-label PR type (bugfix/hardware-support/enhancement) + # ───────────────────────────────────────────────────────────────────────── + - name: Classify PR for labeling + if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') + uses: actions/ai-inference@v2 + id: classify + continue-on-error: true + with: + max-tokens: 30 + prompt: | + Classify this pull request into exactly one category. + + Return exactly one of: bugfix, hardware-support, enhancement + + Use bugfix if it fixes a bug, crash, or incorrect behavior. + Use hardware-support if it adds or improves support for a specific hardware device/variant. + Use enhancement if it adds a new feature, improves performance, or refactors code. + + Title: ${{ github.event.pull_request.title }} + Body: ${{ github.event.pull_request.body }} + system-prompt: You classify pull requests into categories. Be conservative and pick the most appropriate single label. + model: openai/gpt-4o-mini + + - name: Apply type label + if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.classify.outputs.response != '' + uses: actions/github-script@v8 + env: + TYPE_LABEL: ${{ steps.classify.outputs.response }} + with: + script: | + const label = (process.env.TYPE_LABEL || '').trim().toLowerCase(); + const labelMeta = { + 'bugfix': { color: 'd73a4a', description: 'Bug fix' }, + 'hardware-support': { color: '0e8a16', description: 'Hardware support addition or improvement' }, + 'enhancement': { color: 'a2eeef', description: 'New feature or enhancement' }, + }; + const meta = labelMeta[label]; + if (!meta) return; + + // Ensure label exists + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch (e) { + if (e.status !== 404) throw e; + await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description }); + } + + // Apply label + await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] }); From c1e3f56324eeb2d64c2818ee6377af697575fbc0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 11:08:07 -0600 Subject: [PATCH 037/223] Update LovyanGFX to v1.2.19 (#9405) 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_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 +- 16 files changed, 17 insertions(+), 17 deletions(-) diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index 4218e8503..a14e407a1 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.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index 4544d73b5..5e41d5f2b 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -36,4 +36,4 @@ lib_ignore = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32/wiphone/platformio.ini b/variants/esp32/wiphone/platformio.ini index fcf36a23c..fbd77be75 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.7 + lovyan03/LovyanGFX@1.2.19 # 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 4495a409f..20b3b4606 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -126,7 +126,7 @@ 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.0 + lovyan03/LovyanGFX@1.2.19 # 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 ; TODO revert to official device-ui (when merged) diff --git a/variants/esp32s3/heltec_wireless_tracker/platformio.ini b/variants/esp32s3/heltec_wireless_tracker/platformio.ini index c2dab0c93..33643c541 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.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_V1_0/platformio.ini index efeb63b8e..ab6592afb 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.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini index 0b486618b..a5277ba19 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini +++ b/variants/esp32s3/heltec_wireless_tracker_v2/platformio.ini @@ -20,4 +20,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 diff --git a/variants/esp32s3/mesh-tab/platformio.ini b/variants/esp32s3/mesh-tab/platformio.ini index 21d8fb432..ecf5498b3 100644 --- a/variants/esp32s3/mesh-tab/platformio.ini +++ b/variants/esp32s3/mesh-tab/platformio.ini @@ -51,7 +51,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.7 + lovyan03/LovyanGFX@1.2.19 [mesh_tab_xpt2046] extends = mesh_tab_base diff --git a/variants/esp32s3/picomputer-s3/platformio.ini b/variants/esp32s3/picomputer-s3/platformio.ini index bef7b19a0..82147f222 100644 --- a/variants/esp32s3/picomputer-s3/platformio.ini +++ b/variants/esp32s3/picomputer-s3/platformio.ini @@ -24,7 +24,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 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 e4ff9812c..8423bb4df 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini +++ b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini @@ -16,7 +16,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX - lovyan03/LovyanGFX@1.2.7 + lovyan03/LovyanGFX@1.2.19 [ft5x06] extends = mesh_tab_base diff --git a/variants/esp32s3/t-deck/platformio.ini b/variants/esp32s3/t-deck/platformio.ini index 58335796a..5a96a3839 100644 --- a/variants/esp32s3/t-deck/platformio.ini +++ b/variants/esp32s3/t-deck/platformio.ini @@ -24,7 +24,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.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio earlephilhower/ESP8266Audio@1.9.9 # 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 9c7a642b2..8c79ca097 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.7 + lovyan03/LovyanGFX@1.2.19 # 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 ea6369f29..cf8be67fb 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/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.7 + lovyan03/LovyanGFX@1.2.19 # renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio earlephilhower/ESP8266Audio@1.9.9 # 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 419a3539b..c006cf835 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.7 + lovyan03/LovyanGFX@1.2.19 [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.7 + lovyan03/LovyanGFX@1.2.19 [env:tracksenger-oled] custom_meshtastic_hw_model = 48 diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index 28be1f3e1..924dfa74f 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.0 + lovyan03/LovyanGFX@1.2.19 # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index cc6c39aa2..f99ee9a6f 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.7 + lovyan03/LovyanGFX@1.2.19 # 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/af9bc27c9c30fa90772279925b7c5913dff789b4.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library From a922751afc1b87a4e8c21ff94440a221e580233c Mon Sep 17 00:00:00 2001 From: Jason P Date: Wed, 28 Jan 2026 11:12:02 -0600 Subject: [PATCH 038/223] External Notification - handleReceived Rewrite (#9454) * First steps in consolidating code and minimizing rewrite * Continuing code cleanup * Merge containsBell and !isMuted to a single code path * Forgot about alert_message_buzzer in the cleanup * More code refinements and cleanup * Fix nagCycleCutoff * CoPilot Updates --- src/modules/ExternalNotificationModule.cpp | 126 +++++++-------------- 1 file changed, 38 insertions(+), 88 deletions(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 8b7ce700a..5e6985bdf 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -442,6 +442,7 @@ ExternalNotificationModule::ExternalNotificationModule() ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp) { + // Trigger external notification if enabled and not muted; isSilenced is from temporary mute toggles if (moduleConfig.external_notification.enabled && !isSilenced) { #ifdef T_WATCH_S3 drv.setWaveform(0, 75); @@ -456,6 +457,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP for (size_t i = 0; i < p.payload.size; i++) { if (p.payload.bytes[i] == ASCII_BELL) { containsBell = true; + break; } } @@ -465,90 +467,47 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP // If we receive a broadcast message, apply channel mute setting // If we receive a direct message and the receipent is us, apply DM mute setting // Else we just handle it as not muted. - const bool directToUs = !isBroadcast(mp.to) && isToUs(&mp); - bool is_muted = directToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0)) - : (ch.settings.has_module_settings && ch.settings.module_settings.is_muted); + const bool isDmToUs = !isBroadcast(mp.to) && isToUs(&mp); + bool is_muted = isDmToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0)) + : (ch.settings.has_module_settings && ch.settings.module_settings.is_muted); - if (moduleConfig.external_notification.alert_bell) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell"); + const bool buzzerModeIsDirectOnly = + (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY); + + if (containsBell || !is_muted) { + if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message || + moduleConfig.external_notification.alert_bell_vibra || + moduleConfig.external_notification.alert_message_vibra || + ((moduleConfig.external_notification.alert_bell_buzzer || + moduleConfig.external_notification.alert_message_buzzer) && + canBuzz())) { + nagCycleCutoff = millis() + (moduleConfig.external_notification.nag_timeout + ? (moduleConfig.external_notification.nag_timeout * 1000) + : moduleConfig.external_notification.output_ms); + LOG_INFO("Toggling nagCycleCutoff to %lu", nagCycleCutoff); isNagging = true; + } + + if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_message) { + LOG_INFO("externalNotificationModule - Notification Module or Bell"); setExternalState(0, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } } - } - if (moduleConfig.external_notification.alert_bell_vibra) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell (Vibra)"); - isNagging = true; + if (moduleConfig.external_notification.alert_bell_vibra || + moduleConfig.external_notification.alert_message_vibra) { + LOG_INFO("externalNotificationModule - Notification Module or Bell (Vibra)"); setExternalState(1, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; + } + + if ((moduleConfig.external_notification.alert_bell_buzzer || + moduleConfig.external_notification.alert_message_buzzer) && + canBuzz()) { + LOG_INFO("externalNotificationModule - Notification Module or Bell (Buzzer)"); + if (buzzerModeIsDirectOnly && !isDmToUs && !containsBell) { + LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY"); } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - } - - if (moduleConfig.external_notification.alert_bell_buzzer && canBuzz()) { - if (containsBell) { - LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)"); - isNagging = true; - if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { - setExternalState(2, true); - } else { -#ifdef HAS_I2S - if (moduleConfig.external_notification.use_i2s_as_buzzer) { - audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); - } else -#endif - if (moduleConfig.external_notification.use_pwm) { - rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); - } - } - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - } - - if (moduleConfig.external_notification.alert_message && !is_muted) { - LOG_INFO("externalNotificationModule - Notification Module"); - isNagging = true; - setExternalState(0, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - - if (moduleConfig.external_notification.alert_message_vibra && !is_muted) { - LOG_INFO("externalNotificationModule - Notification Module (Vibra)"); - isNagging = true; - setExternalState(1, true); - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } - - if (moduleConfig.external_notification.alert_message_buzzer && !is_muted) { - LOG_INFO("externalNotificationModule - Notification Module (Buzzer)"); - if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || - (!isBroadcast(mp.to) && isToUs(&mp))) { - // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us - isNagging = true; + // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us #ifdef T_LORA_PAGER - if (canBuzz()) { drv.setWaveform(0, 16); // Long buzzer 100% drv.setWaveform(1, 0); // Pause drv.setWaveform(2, 16); @@ -558,11 +517,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP drv.setWaveform(6, 16); drv.setWaveform(7, 0); drv.go(); - } #endif - if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { - setExternalState(2, true); - } else { #ifdef HAS_I2S if (moduleConfig.external_notification.use_i2s_as_buzzer) { audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); @@ -570,18 +525,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP #endif if (moduleConfig.external_notification.use_pwm) { rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); + } else { + setExternalState(2, true); } } - if (moduleConfig.external_notification.nag_timeout) { - nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000; - } else { - nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; - } - } else { - // Don't beep if buzzer mode is "direct messages only" and it is no direct message - LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY"); } } + setIntervalFromNow(0); // run once so we know if we should do something } } else { From 571c1ac34c19461740236a1906b82e8c87f35f84 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 14:08:32 -0600 Subject: [PATCH 039/223] Initial serialModule cleanup (#9465) * Initial serialModule cleanup * Move SERIAL_PRINT_PORT definition to variant.h * Add missed c6 check * Update src/modules/SerialModule.cpp Compile error for invalid SERIAL_PRINT_OBJECT value Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/modules/SerialModule.cpp | 53 ++++++++----------- variants/esp32c6/m5stack_unitc6l/variant.h | 2 + variants/esp32c6/tlora_c6/variant.h | 2 + .../esp32s3/ELECROW-ThinkNode-M5/variant.h | 2 + .../nrf52840/ELECROW-ThinkNode-M1/variant.h | 2 + .../nrf52840/ELECROW-ThinkNode-M3/variant.h | 2 + .../nrf52840/ELECROW-ThinkNode-M4/variant.h | 2 + variants/nrf52840/canaryone/variant.h | 2 + variants/nrf52840/heltec_mesh_solar/variant.h | 2 + variants/nrf52840/meshlink/variant.h | 1 + variants/nrf52840/muzi_base/variant.h | 2 + variants/nrf52840/t-echo-lite/variant.h | 2 + variants/nrf52840/t-echo-plus/variant.h | 2 + variants/nrf52840/t-echo/variant.h | 1 + variants/stm32/CDEBYTE_E77-MBL/variant.h | 2 + variants/stm32/rak3172/variant.h | 1 + 16 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 5699f3be6..7a969343e 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -63,29 +63,26 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; -#if defined(TTGO_T_ECHO) || defined(TTGO_T_ECHO_PLUS) || defined(CANARYONE) || defined(MESHLINK) || \ - defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M5) || \ - defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE) - -SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial; -#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL) -SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial1; -#else -SerialModule::SerialModule() : StreamAPI(&Serial2), concurrency::OSThread("Serial") -{ - api_type = TYPE_SERIAL; -} -static Print *serialPrint = &Serial2; +#ifndef SERIAL_PRINT_PORT +#define SERIAL_PRINT_PORT 2 #endif +#if SERIAL_PRINT_PORT == 0 +#define SERIAL_PRINT_OBJECT Serial +#elif SERIAL_PRINT_PORT == 1 +#define SERIAL_PRINT_OBJECT Serial1 +#elif SERIAL_PRINT_PORT == 2 +#define SERIAL_PRINT_OBJECT Serial2 +#else +#error "Unsupported SERIAL_PRINT_PORT value. Allowed values are 0, 1, or 2." +#endif + +SerialModule::SerialModule() : StreamAPI(&SERIAL_PRINT_OBJECT), concurrency::OSThread("Serial") +{ + api_type = TYPE_SERIAL; +} +static Print *serialPrint = &SERIAL_PRINT_OBJECT; + char serialBytes[512]; size_t serialPayloadSize; @@ -205,9 +202,7 @@ int32_t SerialModule::runOnce() Serial.begin(baud); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } -#elif !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \ - !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) +#elif SERIAL_PRINT_PORT != 0 if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { #ifdef ARCH_RP2040 @@ -264,9 +259,7 @@ int32_t SerialModule::runOnce() } } -#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \ - !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE) +#if SERIAL_PRINT_PORT != 0 else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); @@ -540,11 +533,7 @@ ParsedLine parseLine(const char *line) */ void SerialModule::processWXSerial() { -#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \ - !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \ - !defined(ELECROW_ThinkNode_M3) && \ - !defined(ELECROW_ThinkNode_M4) && \ - !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE) +#if SERIAL_PRINT_PORT != 0 && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. diff --git a/variants/esp32c6/m5stack_unitc6l/variant.h b/variants/esp32c6/m5stack_unitc6l/variant.h index d973aa281..1654ee590 100644 --- a/variants/esp32c6/m5stack_unitc6l/variant.h +++ b/variants/esp32c6/m5stack_unitc6l/variant.h @@ -50,3 +50,5 @@ void c6l_init(); #endif #define SCREEN_TRANSITION_FRAMERATE 10 #define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness + +#define SERIAL_PRINT_PORT 1 diff --git a/variants/esp32c6/tlora_c6/variant.h b/variants/esp32c6/tlora_c6/variant.h index 55635fe13..4a0d40232 100644 --- a/variants/esp32c6/tlora_c6/variant.h +++ b/variants/esp32c6/tlora_c6/variant.h @@ -19,3 +19,5 @@ #define SX126X_TXEN 14 #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define SERIAL_PRINT_PORT 1 diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 77a64f717..353741d91 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -81,4 +81,6 @@ #define BUTTON_PIN PIN_BUTTON1 #define BUTTON_PIN_ALT PIN_BUTTON2 + +#define SERIAL_PRINT_PORT 0 #endif diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index cde0f49c1..6f7056d27 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -159,6 +159,8 @@ External serial flash WP25R1635FZUIL0 #define PIN_SERIAL1_TX GPS_TX_PIN #define PIN_SERIAL1_RX GPS_RX_PIN +#define SERIAL_PRINT_PORT 0 + /* * SPI Interfaces */ diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index 29a6c85fd..decdd6ed0 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -113,6 +113,8 @@ extern "C" { #define LR11X0_DIO3_TCXO_VOLTAGE 3.3 #define LR11X0_DIO_AS_RF_SWITCH +#define SERIAL_PRINT_PORT 0 + // PCF8563 RTC Module // REVISIT https://github.com/meshtastic/firmware/pull/9084 // #define PCF8563_RTC 0x51 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h index faca5b075..1aff32838 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h @@ -135,6 +135,8 @@ static const uint8_t A0 = PIN_A0; #define PIN_SERIAL1_RX GPS_RX_PIN #define PIN_SERIAL1_TX GPS_TX_PIN +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 61d1e8df9..537f4e26c 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -170,6 +170,8 @@ static const uint8_t A0 = PIN_A0; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (2.0F) +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 112bcd8b3..c80b81da4 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -142,6 +142,8 @@ No longer populated on PCB #define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin #define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index d1dba574f..2e252ff95 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -54,6 +54,7 @@ extern "C" { */ #define PIN_SERIAL1_RX (32 + 8) #define PIN_SERIAL1_TX (7) +#define SERIAL_PRINT_PORT 0 /* * SPI Interfaces diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index 96604c400..8a3532b77 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -176,6 +176,8 @@ extern "C" { #define EXTERNAL_FLASH_DEVICES W25Q32JVSS #define EXTERNAL_FLASH_USE_QSPI +#define SERIAL_PRINT_PORT 0 + // NFC is disabled via CONFIG_NFCT_PINS_AS_GPIOS=1 build flag // This configures P0.09 and P0.10 as regular GPIO pins instead of NFC pins diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 0748f6d48..12dcb8949 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -171,6 +171,8 @@ static const uint8_t A0 = PIN_A0; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (2.0F) +#define SERIAL_PRINT_PORT 0 + // #define NO_EXT_GPIO 1 // PINs back side // Batt & solar connector left up corner diff --git a/variants/nrf52840/t-echo-plus/variant.h b/variants/nrf52840/t-echo-plus/variant.h index 226f6d6fe..2d00f8c1f 100644 --- a/variants/nrf52840/t-echo-plus/variant.h +++ b/variants/nrf52840/t-echo-plus/variant.h @@ -138,6 +138,8 @@ static const uint8_t A0 = PIN_A0; // Battery / ADC already defined above #define HAS_RTC 1 +#define SERIAL_PRINT_PORT 0 + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 9244fc6c3..8e72fd84b 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -88,6 +88,7 @@ static const uint8_t A0 = PIN_A0; /* * Serial interfaces */ +#define SERIAL_PRINT_PORT 0 /* No longer populated on PCB diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index e3d111a33..2cf355a61 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -19,5 +19,7 @@ Do not expect a working Meshtastic device with this target. // #define LED_PIN PB3 // LED2 #define LED_STATE_ON 1 +#define SERIAL_PRINT_PORT 1 + #define EBYTE_E77_MBL #endif diff --git a/variants/stm32/rak3172/variant.h b/variants/stm32/rak3172/variant.h index 30d2b57b4..b3f6cbcda 100644 --- a/variants/stm32/rak3172/variant.h +++ b/variants/stm32/rak3172/variant.h @@ -17,5 +17,6 @@ Do not expect a working Meshtastic device with this target. #define LED_STATE_ON 1 #define RAK3172 +#define SERIAL_PRINT_PORT 1 #endif From f710cd6ecbd415d9e1c1fc9ec9a028f4b0bd3003 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Wed, 28 Jan 2026 12:14:01 -0800 Subject: [PATCH 040/223] Support fully direct request/responses (#9455) Co-authored-by: Ben Meadors --- src/mesh/ReliableRouter.cpp | 6 ------ src/mesh/Router.cpp | 7 +++++++ src/modules/RoutingModule.cpp | 2 ++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 2b9b17183..42c24c783 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -17,12 +17,6 @@ ErrorCode ReliableRouter::send(meshtastic_MeshPacket *p) { if (p->want_ack) { - // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our - // message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop - // counts and we want this message to get through the whole mesh, so use the default. - if (p->hop_limit == 0) { - p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); - } DEBUG_HEAP_BEFORE; auto copy = packetPool.allocCopy(*p); DEBUG_HEAP_AFTER("ReliableRouter::send", copy); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1a3270040..287fbcf60 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -266,6 +266,13 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) } } + // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our + // message will rebroadcast. But asking for hop_limit 0 in that context means the client app has no preference on hop + // counts and we want this message to get through the whole mesh, so use the default. + if (src == RX_SRC_USER && p->want_ack && p->hop_limit == 0) { + p->hop_limit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); + } + return send(p); } } diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index e9e1fc786..d87cf3a44 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -67,6 +67,8 @@ uint8_t RoutingModule::getHopLimitForResponse(const meshtastic_MeshPacket &mp) #if !(EVENTMODE) // This falls through to the default. return hopsUsed; // If the request used more hops than the limit, use the same amount of hops #endif + } else if (mp.hop_start == 0) { + return 0; // The requesting node wanted 0 hops, so the response also uses a direct/local path. } else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) { return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different } From 1d219a93abd2b819619d7918dc7bad912be687d9 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 14:58:05 -0600 Subject: [PATCH 041/223] Move input init to an init function in InputBroker (#9463) * Move input init to an init function in iInputBroker * Unbreak targets with EXCLUDE_INPUTBROKER * Update src/input/InputBroker.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix conditional compilation for input broker * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Trunk --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/input/InputBroker.cpp | 310 ++++++++++++++++++++++++++++++++++++++ src/input/InputBroker.h | 5 +- src/main.cpp | 226 +-------------------------- src/modules/Modules.cpp | 76 ---------- 4 files changed, 317 insertions(+), 300 deletions(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index 0aa78e2b8..c0a56233f 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -1,8 +1,59 @@ #include "InputBroker.h" #include "PowerFSM.h" // needed for event trigger #include "configuration.h" +#include "graphics/Screen.h" #include "modules/ExternalNotificationModule.h" +#if ARCH_PORTDUINO +#include "input/LinuxInputImpl.h" +#include "input/SeesawRotary.h" +#include "platform/portduino/PortduinoGlue.h" +#endif + +#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#include "input/ExpressLRSFiveWay.h" +#include "input/RotaryEncoderImpl.h" +#include "input/RotaryEncoderInterruptImpl1.h" +#include "input/SerialKeyboardImpl.h" +#include "input/UpDownInterruptImpl1.h" +#include "input/i2cButton.h" +#if HAS_TRACKBALL +#include "input/TrackballInterruptImpl1.h" +#endif + +#include "modules/StatusLEDModule.h" + +#if !MESHTASTIC_EXCLUDE_I2C +#include "input/cardKbI2cImpl.h" +#endif +#include "input/kbMatrixImpl.h" +#endif + +#if HAS_BUTTON || defined(ARCH_PORTDUINO) +#include "input/ButtonThread.h" + +#if defined(BUTTON_PIN_TOUCH) +ButtonThread *TouchButtonThread = nullptr; +#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) +static bool touchBacklightWasOn = false; +static bool touchBacklightActive = false; +#endif +#endif + +#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) +ButtonThread *UserButtonThread = nullptr; +#endif + +#if defined(ALT_BUTTON_PIN) +ButtonThread *BackButtonThread = nullptr; +#endif + +#if defined(CANCEL_BUTTON_PIN) +ButtonThread *CancelButtonThread = nullptr; +#endif + +#endif + InputBroker *inputBroker = nullptr; InputBroker::InputBroker() @@ -74,3 +125,262 @@ void InputBroker::pollSoonWorker(void *p) vTaskDelete(NULL); } #endif + +void InputBroker::Init() +{ + +#ifdef BUTTON_PIN +#ifdef ARCH_ESP32 + +#if ESP_ARDUINO_VERSION_MAJOR >= 3 +#ifdef BUTTON_NEED_PULLUP + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#endif +#else + pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN +#ifdef BUTTON_NEED_PULLUP + gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); + delay(10); +#endif +#ifdef BUTTON_NEED_PULLUP2 + gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2); + delay(10); +#endif +#endif +#endif +#endif + +// buttons are now inputBroker, so have to come after setupModules +#if HAS_BUTTON + int pullup_sense = 0; +#ifdef INPUT_PULLUP_SENSE + // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did +#ifdef BUTTON_SENSE_TYPE + pullup_sense = BUTTON_SENSE_TYPE; +#else + pullup_sense = INPUT_PULLUP_SENSE; +#endif +#endif +#if defined(ARCH_PORTDUINO) + + if (portduino_config.userButtonPin.enabled) { + + LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin); + UserButtonThread = new ButtonThread("UserButton"); + if (screen) { + ButtonConfig config; + config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin; + config.activeLow = true; + config.activePullup = true; + config.pullupSense = INPUT_PULLUP; + config.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + config.singlePress = INPUT_BROKER_USER_PRESS; + config.longPress = INPUT_BROKER_SELECT; + UserButtonThread->initButton(config); + } + } +#endif + +#ifdef BUTTON_PIN_TOUCH + TouchButtonThread = new ButtonThread("BackButton"); + ButtonConfig touchConfig; + touchConfig.pinNumber = BUTTON_PIN_TOUCH; + touchConfig.activeLow = true; + touchConfig.activePullup = true; + touchConfig.pullupSense = pullup_sense; + touchConfig.intRoutine = []() { + TouchButtonThread->userButton.tick(); + TouchButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + touchConfig.singlePress = INPUT_BROKER_NONE; + touchConfig.longPress = INPUT_BROKER_BACK; +#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) + // On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds + touchConfig.longPress = INPUT_BROKER_NONE; + touchConfig.suppressLeadUpSound = true; + touchConfig.onPress = []() { + touchBacklightWasOn = uiconfig.screen_brightness == 1; + if (!touchBacklightWasOn) { + digitalWrite(PIN_EINK_EN, HIGH); + } + touchBacklightActive = true; + }; + touchConfig.onRelease = []() { + if (touchBacklightActive && !touchBacklightWasOn) { + digitalWrite(PIN_EINK_EN, LOW); + } + touchBacklightActive = false; + }; +#endif + TouchButtonThread->initButton(touchConfig); +#endif + +#if defined(CANCEL_BUTTON_PIN) + // Buttons. Moved here cause we need NodeDB to be initialized + CancelButtonThread = new ButtonThread("CancelButton"); + ButtonConfig cancelConfig; + cancelConfig.pinNumber = CANCEL_BUTTON_PIN; + cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW; + cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP; + cancelConfig.pullupSense = pullup_sense; + cancelConfig.intRoutine = []() { + CancelButtonThread->userButton.tick(); + CancelButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + cancelConfig.singlePress = INPUT_BROKER_CANCEL; + cancelConfig.longPress = INPUT_BROKER_SHUTDOWN; + cancelConfig.longPressTime = 4000; + CancelButtonThread->initButton(cancelConfig); +#endif + +#if defined(ALT_BUTTON_PIN) + // Buttons. Moved here cause we need NodeDB to be initialized + BackButtonThread = new ButtonThread("BackButton"); + ButtonConfig backConfig; + backConfig.pinNumber = ALT_BUTTON_PIN; + backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW; + backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP; + backConfig.pullupSense = pullup_sense; + backConfig.intRoutine = []() { + BackButtonThread->userButton.tick(); + BackButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + backConfig.singlePress = INPUT_BROKER_ALT_PRESS; + backConfig.longPress = INPUT_BROKER_ALT_LONG; + backConfig.longPressTime = 500; + BackButtonThread->initButton(backConfig); +#endif + +#if defined(BUTTON_PIN) +#if defined(USERPREFS_BUTTON_PIN) + int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; +#else + int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; +#endif +#ifndef BUTTON_ACTIVE_LOW +#define BUTTON_ACTIVE_LOW true +#endif +#ifndef BUTTON_ACTIVE_PULLUP +#define BUTTON_ACTIVE_PULLUP true +#endif + + // Buttons. Moved here cause we need NodeDB to be initialized + // If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP + UserButtonThread = new ButtonThread("UserButton"); + if (screen) { + ButtonConfig userConfig; + userConfig.pinNumber = (uint8_t)_pinNum; + userConfig.activeLow = BUTTON_ACTIVE_LOW; + userConfig.activePullup = BUTTON_ACTIVE_PULLUP; + userConfig.pullupSense = pullup_sense; + userConfig.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + userConfig.singlePress = INPUT_BROKER_USER_PRESS; + userConfig.longPress = INPUT_BROKER_SELECT; + userConfig.longPressTime = 500; + userConfig.longLongPress = INPUT_BROKER_SHUTDOWN; + UserButtonThread->initButton(userConfig); + } else { + ButtonConfig userConfigNoScreen; + userConfigNoScreen.pinNumber = (uint8_t)_pinNum; + userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW; + userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP; + userConfigNoScreen.pullupSense = pullup_sense; + userConfigNoScreen.intRoutine = []() { + UserButtonThread->userButton.tick(); + UserButtonThread->setIntervalFromNow(0); + runASAP = true; + BaseType_t higherWake = 0; + concurrency::mainDelay.interruptFromISR(&higherWake); + }; + userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS; + userConfigNoScreen.longPress = INPUT_BROKER_NONE; + userConfigNoScreen.longPressTime = 500; + userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN; + userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING; + userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE; + UserButtonThread->initButton(userConfigNoScreen); + } +#endif +#endif + +#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { +#if defined(T_LORA_PAGER) + // use a special FSM based rotary encoder version for T-LoRa Pager + rotaryEncoderImpl = new RotaryEncoderImpl(); + if (!rotaryEncoderImpl->init()) { + delete rotaryEncoderImpl; + rotaryEncoderImpl = nullptr; + } +#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2) + upDownInterruptImpl1 = new UpDownInterruptImpl1(); + if (!upDownInterruptImpl1->init()) { + delete upDownInterruptImpl1; + upDownInterruptImpl1 = nullptr; + } +#else + rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); + if (!rotaryEncoderInterruptImpl1->init()) { + delete rotaryEncoderInterruptImpl1; + rotaryEncoderInterruptImpl1 = nullptr; + } +#endif + cardKbI2cImpl = new CardKbI2cImpl(); + cardKbI2cImpl->init(); +#if defined(M5STACK_UNITC6L) + i2cButton = new i2cButtonThread("i2cButtonThread"); +#endif +#ifdef INPUTBROKER_MATRIX_TYPE + kbMatrixImpl = new KbMatrixImpl(); + kbMatrixImpl->init(); +#endif // INPUTBROKER_MATRIX_TYPE +#ifdef INPUTBROKER_SERIAL_TYPE + aSerialKeyboardImpl = new SerialKeyboardImpl(); + aSerialKeyboardImpl->init(); +#endif // INPUTBROKER_MATRIX_TYPE + } +#endif // HAS_BUTTON +#if ARCH_PORTDUINO + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") { + seesawRotary = new SeesawRotary("SeesawRotary"); + if (!seesawRotary->init()) { + delete seesawRotary; + seesawRotary = nullptr; + } + aLinuxInputImpl = new LinuxInputImpl(); + aLinuxInputImpl->init(); + } +#endif +#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL + if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { + trackballInterruptImpl1 = new TrackballInterruptImpl1(); + trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS); + } +#endif +#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE + expressLRSFiveWayInput = new ExpressLRSFiveWay(); +#endif +} diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h index 970e9969a..9fcdd845f 100644 --- a/src/input/InputBroker.h +++ b/src/input/InputBroker.h @@ -1,6 +1,7 @@ #pragma once #include "Observer.h" +#include "concurrency/OSThread.h" #include "freertosinc.h" #ifdef InputBrokerDebug @@ -76,6 +77,7 @@ class InputBroker : public Observable void queueInputEvent(const InputEvent *event); void processInputEventQueue(); #endif + void Init(); protected: int handleInputEvent(const InputEvent *event); @@ -89,4 +91,5 @@ class InputBroker : public Observable #endif }; -extern InputBroker *inputBroker; \ No newline at end of file +extern InputBroker *inputBroker; +extern bool runASAP; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 3ccc06afb..68eda2d0d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -120,31 +120,6 @@ void printPartitionTable() #endif // DEBUG_PARTITION_TABLE #endif // ARCH_ESP32 -#if HAS_BUTTON || defined(ARCH_PORTDUINO) -#include "input/ButtonThread.h" - -#if defined(BUTTON_PIN_TOUCH) -ButtonThread *TouchButtonThread = nullptr; -#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) -static bool touchBacklightWasOn = false; -static bool touchBacklightActive = false; -#endif -#endif - -#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO) -ButtonThread *UserButtonThread = nullptr; -#endif - -#if defined(ALT_BUTTON_PIN) -ButtonThread *BackButtonThread = nullptr; -#endif - -#if defined(CANCEL_BUTTON_PIN) -ButtonThread *CancelButtonThread = nullptr; -#endif - -#endif - #include "AmbientLightingThread.h" #include "PowerFSMThread.h" @@ -509,30 +484,6 @@ void setup() LOG_INFO("Wait for peripherals to stabilize"); delay(PERIPHERAL_WARMUP_MS); #endif - -#ifdef BUTTON_PIN -#ifdef ARCH_ESP32 - -#if ESP_ARDUINO_VERSION_MAJOR >= 3 -#ifdef BUTTON_NEED_PULLUP - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP); -#else - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN -#endif -#else - pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN -#ifdef BUTTON_NEED_PULLUP - gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN)); - delay(10); -#endif -#ifdef BUTTON_NEED_PULLUP2 - gpio_pullup_en((gpio_num_t)BUTTON_NEED_PULLUP2); - delay(10); -#endif -#endif -#endif -#endif - initSPI(); OSThread::setup(); @@ -999,180 +950,9 @@ void setup() nodeDB->hasWarned = true; } #endif - -// buttons are now inputBroker, so have to come after setupModules -#if HAS_BUTTON - int pullup_sense = 0; -#ifdef INPUT_PULLUP_SENSE - // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did -#ifdef BUTTON_SENSE_TYPE - pullup_sense = BUTTON_SENSE_TYPE; -#else - pullup_sense = INPUT_PULLUP_SENSE; -#endif -#endif -#if defined(ARCH_PORTDUINO) - - if (portduino_config.userButtonPin.enabled) { - - LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin); - UserButtonThread = new ButtonThread("UserButton"); - if (screen) { - ButtonConfig config; - config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin; - config.activeLow = true; - config.activePullup = true; - config.pullupSense = INPUT_PULLUP; - config.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - config.singlePress = INPUT_BROKER_USER_PRESS; - config.longPress = INPUT_BROKER_SELECT; - UserButtonThread->initButton(config); - } - } -#endif - -#ifdef BUTTON_PIN_TOUCH - TouchButtonThread = new ButtonThread("BackButton"); - ButtonConfig touchConfig; - touchConfig.pinNumber = BUTTON_PIN_TOUCH; - touchConfig.activeLow = true; - touchConfig.activePullup = true; - touchConfig.pullupSense = pullup_sense; - touchConfig.intRoutine = []() { - TouchButtonThread->userButton.tick(); - TouchButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - touchConfig.singlePress = INPUT_BROKER_NONE; - touchConfig.longPress = INPUT_BROKER_BACK; -#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN) - // On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds - touchConfig.longPress = INPUT_BROKER_NONE; - touchConfig.suppressLeadUpSound = true; - touchConfig.onPress = []() { - touchBacklightWasOn = uiconfig.screen_brightness == 1; - if (!touchBacklightWasOn) { - digitalWrite(PIN_EINK_EN, HIGH); - } - touchBacklightActive = true; - }; - touchConfig.onRelease = []() { - if (touchBacklightActive && !touchBacklightWasOn) { - digitalWrite(PIN_EINK_EN, LOW); - } - touchBacklightActive = false; - }; -#endif - TouchButtonThread->initButton(touchConfig); -#endif - -#if defined(CANCEL_BUTTON_PIN) - // Buttons. Moved here cause we need NodeDB to be initialized - CancelButtonThread = new ButtonThread("CancelButton"); - ButtonConfig cancelConfig; - cancelConfig.pinNumber = CANCEL_BUTTON_PIN; - cancelConfig.activeLow = CANCEL_BUTTON_ACTIVE_LOW; - cancelConfig.activePullup = CANCEL_BUTTON_ACTIVE_PULLUP; - cancelConfig.pullupSense = pullup_sense; - cancelConfig.intRoutine = []() { - CancelButtonThread->userButton.tick(); - CancelButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - cancelConfig.singlePress = INPUT_BROKER_CANCEL; - cancelConfig.longPress = INPUT_BROKER_SHUTDOWN; - cancelConfig.longPressTime = 4000; - CancelButtonThread->initButton(cancelConfig); -#endif - -#if defined(ALT_BUTTON_PIN) - // Buttons. Moved here cause we need NodeDB to be initialized - BackButtonThread = new ButtonThread("BackButton"); - ButtonConfig backConfig; - backConfig.pinNumber = ALT_BUTTON_PIN; - backConfig.activeLow = ALT_BUTTON_ACTIVE_LOW; - backConfig.activePullup = ALT_BUTTON_ACTIVE_PULLUP; - backConfig.pullupSense = pullup_sense; - backConfig.intRoutine = []() { - BackButtonThread->userButton.tick(); - BackButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - backConfig.singlePress = INPUT_BROKER_ALT_PRESS; - backConfig.longPress = INPUT_BROKER_ALT_LONG; - backConfig.longPressTime = 500; - BackButtonThread->initButton(backConfig); -#endif - -#if defined(BUTTON_PIN) -#if defined(USERPREFS_BUTTON_PIN) - int _pinNum = config.device.button_gpio ? config.device.button_gpio : USERPREFS_BUTTON_PIN; -#else - int _pinNum = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; -#endif -#ifndef BUTTON_ACTIVE_LOW -#define BUTTON_ACTIVE_LOW true -#endif -#ifndef BUTTON_ACTIVE_PULLUP -#define BUTTON_ACTIVE_PULLUP true -#endif - - // Buttons. Moved here cause we need NodeDB to be initialized - // If your variant.h has a BUTTON_PIN defined, go ahead and define BUTTON_ACTIVE_LOW and BUTTON_ACTIVE_PULLUP - UserButtonThread = new ButtonThread("UserButton"); - if (screen) { - ButtonConfig userConfig; - userConfig.pinNumber = (uint8_t)_pinNum; - userConfig.activeLow = BUTTON_ACTIVE_LOW; - userConfig.activePullup = BUTTON_ACTIVE_PULLUP; - userConfig.pullupSense = pullup_sense; - userConfig.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - userConfig.singlePress = INPUT_BROKER_USER_PRESS; - userConfig.longPress = INPUT_BROKER_SELECT; - userConfig.longPressTime = 500; - userConfig.longLongPress = INPUT_BROKER_SHUTDOWN; - UserButtonThread->initButton(userConfig); - } else { - ButtonConfig userConfigNoScreen; - userConfigNoScreen.pinNumber = (uint8_t)_pinNum; - userConfigNoScreen.activeLow = BUTTON_ACTIVE_LOW; - userConfigNoScreen.activePullup = BUTTON_ACTIVE_PULLUP; - userConfigNoScreen.pullupSense = pullup_sense; - userConfigNoScreen.intRoutine = []() { - UserButtonThread->userButton.tick(); - UserButtonThread->setIntervalFromNow(0); - runASAP = true; - BaseType_t higherWake = 0; - mainDelay.interruptFromISR(&higherWake); - }; - userConfigNoScreen.singlePress = INPUT_BROKER_USER_PRESS; - userConfigNoScreen.longPress = INPUT_BROKER_NONE; - userConfigNoScreen.longPressTime = 500; - userConfigNoScreen.longLongPress = INPUT_BROKER_SHUTDOWN; - userConfigNoScreen.doublePress = INPUT_BROKER_SEND_PING; - userConfigNoScreen.triplePress = INPUT_BROKER_GPS_TOGGLE; - UserButtonThread->initButton(userConfigNoScreen); - } -#endif - +#if !MESHTASTIC_EXCLUDE_INPUTBROKER + if (inputBroker) + inputBroker->Init(); #endif #ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e17868baf..c3463a07e 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,24 +1,7 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "buzz/BuzzerFeedbackThread.h" -#include "input/ExpressLRSFiveWay.h" -#include "input/InputBroker.h" -#include "input/RotaryEncoderImpl.h" -#include "input/RotaryEncoderInterruptImpl1.h" -#include "input/SerialKeyboardImpl.h" -#include "input/UpDownInterruptImpl1.h" -#include "input/i2cButton.h" #include "modules/SystemCommandsModule.h" -#if HAS_TRACKBALL -#include "input/TrackballInterruptImpl1.h" -#endif - -#include "modules/StatusLEDModule.h" - -#if !MESHTASTIC_EXCLUDE_I2C -#include "input/cardKbI2cImpl.h" -#endif -#include "input/kbMatrixImpl.h" #endif #if !MESHTASTIC_EXCLUDE_PKI #include "KeyVerificationModule.h" @@ -59,8 +42,6 @@ #include "modules/WaypointModule.h" #endif #if ARCH_PORTDUINO -#include "input/LinuxInputImpl.h" -#include "input/SeesawRotary.h" #include "modules/Telemetry/HostMetrics.h" #if !MESHTASTIC_EXCLUDE_STOREFORWARD #include "modules/StoreForwardModule.h" @@ -179,63 +160,6 @@ void setupModules() #endif // Example: Put your module here // new ReplyModule(); -#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { -#if defined(T_LORA_PAGER) - // use a special FSM based rotary encoder version for T-LoRa Pager - rotaryEncoderImpl = new RotaryEncoderImpl(); - if (!rotaryEncoderImpl->init()) { - delete rotaryEncoderImpl; - rotaryEncoderImpl = nullptr; - } -#elif defined(INPUTDRIVER_ENCODER_TYPE) && (INPUTDRIVER_ENCODER_TYPE == 2) - upDownInterruptImpl1 = new UpDownInterruptImpl1(); - if (!upDownInterruptImpl1->init()) { - delete upDownInterruptImpl1; - upDownInterruptImpl1 = nullptr; - } -#else - rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); - if (!rotaryEncoderInterruptImpl1->init()) { - delete rotaryEncoderInterruptImpl1; - rotaryEncoderInterruptImpl1 = nullptr; - } -#endif - cardKbI2cImpl = new CardKbI2cImpl(); - cardKbI2cImpl->init(); -#if defined(M5STACK_UNITC6L) - i2cButton = new i2cButtonThread("i2cButtonThread"); -#endif -#ifdef INPUTBROKER_MATRIX_TYPE - kbMatrixImpl = new KbMatrixImpl(); - kbMatrixImpl->init(); -#endif // INPUTBROKER_MATRIX_TYPE -#ifdef INPUTBROKER_SERIAL_TYPE - aSerialKeyboardImpl = new SerialKeyboardImpl(); - aSerialKeyboardImpl->init(); -#endif // INPUTBROKER_MATRIX_TYPE - } -#endif // HAS_BUTTON -#if ARCH_PORTDUINO - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR && portduino_config.i2cdev != "") { - seesawRotary = new SeesawRotary("SeesawRotary"); - if (!seesawRotary->init()) { - delete seesawRotary; - seesawRotary = nullptr; - } - aLinuxInputImpl = new LinuxInputImpl(); - aLinuxInputImpl->init(); - } -#endif -#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL - if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { - trackballInterruptImpl1 = new TrackballInterruptImpl1(); - trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS); - } -#endif -#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE - expressLRSFiveWayInput = new ExpressLRSFiveWay(); -#endif #if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { cannedMessageModule = new CannedMessageModule(); From 4fd0a8276bc8f64220ad42734c9315edd22e0395 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 17:09:13 -0600 Subject: [PATCH 042/223] Just set LED_BUILTIN universally to -1, as we don't use it. (#8830) * Just set LED_BUILTIN universally to -1, as we don't use it. * LUD_BUILTIN workarounds * Squash the LED_BUILTINs that sneaked in * Don't kill valid pin derfine --- platformio.ini | 1 + variants/esp32/rak11200/pins_arduino.h | 2 -- variants/esp32/rak11200/variant.h | 2 -- variants/esp32/tbeam/platformio.ini | 1 + variants/esp32c6/tlora_c6/platformio.ini | 1 + variants/esp32s3/heltec_v3/platformio.ini | 1 + variants/esp32s3/heltec_v4/pins_arduino.h | 4 ---- variants/esp32s3/heltec_vision_master_e213/pins_arduino.h | 4 ---- variants/esp32s3/heltec_vision_master_e290/pins_arduino.h | 4 ---- variants/esp32s3/heltec_vision_master_t190/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_paper/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_tracker/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h | 4 ---- variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h | 4 ---- variants/esp32s3/m5stack_cores3/pins_arduino.h | 5 +---- variants/esp32s3/mesh-tab/pins_arduino.h | 4 ---- variants/esp32s3/rak3312/pins_arduino.h | 3 --- variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h | 3 --- variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h | 2 -- variants/esp32s3/t-deck/pins_arduino.h | 2 -- variants/esp32s3/t-watch-s3/pins_arduino.h | 2 -- variants/esp32s3/tracksenger/internal/pins_arduino.h | 4 ---- variants/esp32s3/tracksenger/lcd/pins_arduino.h | 4 ---- variants/esp32s3/tracksenger/oled/pins_arduino.h | 4 ---- variants/esp32s3/unphone/pins_arduino.h | 3 --- variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M1/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M3/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M4/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M6/variant.h | 1 - variants/nrf52840/ME25LS01-4Y10TD/variant.h | 1 - variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h | 1 - variants/nrf52840/MS24SF1/variant.h | 1 - variants/nrf52840/MakePython_nRF52840_eink/variant.h | 1 - variants/nrf52840/MakePython_nRF52840_oled/variant.h | 1 - variants/nrf52840/TWC_mesh_v4/variant.h | 1 - variants/nrf52840/canaryone/variant.h | 1 - variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h | 1 - .../nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h | 1 - variants/nrf52840/dls_Minimesh_Lite/variant.h | 1 - variants/nrf52840/feather_diy/variant.h | 2 -- variants/nrf52840/gat562_mesh_trial_tracker/variant.h | 1 - variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h | 1 - variants/nrf52840/heltec_mesh_node_t114/variant.h | 1 - variants/nrf52840/heltec_mesh_pocket/variant.h | 1 - variants/nrf52840/heltec_mesh_solar/variant.h | 1 - variants/nrf52840/meshlink/variant.h | 1 - variants/nrf52840/meshtiny/variant.h | 1 - variants/nrf52840/monteops_hw1/variant.h | 1 - variants/nrf52840/muzi_base/variant.h | 1 - variants/nrf52840/nano-g2-ultra/variant.h | 1 - variants/nrf52840/r1-neo/variant.h | 1 - variants/nrf52840/rak2560/variant.h | 1 - variants/nrf52840/rak3401_1watt/variant.h | 1 - variants/nrf52840/rak4631/variant.h | 1 - variants/nrf52840/rak4631_epaper/variant.h | 1 - variants/nrf52840/rak4631_epaper_onrxtx/variant.h | 1 - variants/nrf52840/rak4631_eth_gw/variant.h | 1 - variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h | 1 - variants/nrf52840/rak_wismeshtag/variant.h | 1 - variants/nrf52840/rak_wismeshtap/variant.h | 1 - variants/nrf52840/seeed_solar_node/variant.h | 1 - variants/nrf52840/seeed_wio_tracker_L1/variant.h | 1 - variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h | 1 - variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h | 2 -- variants/nrf52840/t-echo-lite/variant.h | 1 - variants/nrf52840/t-echo-plus/variant.h | 1 - variants/nrf52840/t-echo/variant.h | 1 - variants/nrf52840/tracker-t1000-e/variant.h | 1 - variants/nrf52840/wio-sdk-wm1110/variant.h | 2 -- variants/nrf52840/wio-t1000-s/variant.h | 1 - variants/nrf52840/wio-tracker-wm1110/variant.h | 2 -- variants/rp2040/challenger_2040_lora/pins_arduino.h | 2 -- variants/rp2040/rak11310/pins_arduino.h | 1 - variants/rp2040/rak11310/variant.h | 2 +- variants/rp2040/rpipicow/platformio.ini | 1 + variants/rp2040/rpipicow/variant.h | 2 +- variants/rp2040/senselora_rp2040/pins_arduino.h | 1 - variants/stm32/wio-e5/variant.h | 5 ----- 80 files changed, 8 insertions(+), 137 deletions(-) diff --git a/platformio.ini b/platformio.ini index b7c46a6e1..25fc8829f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -56,6 +56,7 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage + -DLED_BUILTIN=-1 #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now #-D OLED_PL=1 #-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs diff --git a/variants/esp32/rak11200/pins_arduino.h b/variants/esp32/rak11200/pins_arduino.h index f383d54a7..263fbd5a0 100644 --- a/variants/esp32/rak11200/pins_arduino.h +++ b/variants/esp32/rak11200/pins_arduino.h @@ -6,8 +6,6 @@ #define LED_GREEN 12 #define LED_BLUE 2 -#define LED_BUILTIN LED_GREEN - static const uint8_t TX = 1; static const uint8_t RX = 3; diff --git a/variants/esp32/rak11200/variant.h b/variants/esp32/rak11200/variant.h index 01edb8b73..fe7d05676 100644 --- a/variants/esp32/rak11200/variant.h +++ b/variants/esp32/rak11200/variant.h @@ -6,8 +6,6 @@ #define LED_GREEN 12 #define LED_BLUE 2 -#define LED_BUILTIN LED_GREEN - static const uint8_t TX = 1; static const uint8_t RX = 3; diff --git a/variants/esp32/tbeam/platformio.ini b/variants/esp32/tbeam/platformio.ini index 16a3d1845..dbaccee8f 100644 --- a/variants/esp32/tbeam/platformio.ini +++ b/variants/esp32/tbeam/platformio.ini @@ -18,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -I variants/esp32/tbeam -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue + -ULED_BUILTIN upload_speed = 921600 [env:tbeam-displayshield] diff --git a/variants/esp32c6/tlora_c6/platformio.ini b/variants/esp32c6/tlora_c6/platformio.ini index 6b402d7c5..174e5e297 100644 --- a/variants/esp32c6/tlora_c6/platformio.ini +++ b/variants/esp32c6/tlora_c6/platformio.ini @@ -8,3 +8,4 @@ build_flags = -I variants/esp32c6/tlora_c6 -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 + -ULED_BUILTIN diff --git a/variants/esp32s3/heltec_v3/platformio.ini b/variants/esp32s3/heltec_v3/platformio.ini index 2f53c8756..fe31df094 100644 --- a/variants/esp32s3/heltec_v3/platformio.ini +++ b/variants/esp32s3/heltec_v3/platformio.ini @@ -18,3 +18,4 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_V3 -I variants/esp32s3/heltec_v3 + -ULED_BUILTIN diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h index d4485016d..32fd8a8e4 100644 --- a/variants/esp32s3/heltec_v4/pins_arduino.h +++ b/variants/esp32s3/heltec_v4/pins_arduino.h @@ -6,10 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h b/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h index 56f5ef157..5cf3c6453 100644 --- a/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_e213/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant -#define BUILTIN_LED LED_BUILTIN // Backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h b/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h index 56f5ef157..5cf3c6453 100644 --- a/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_e290/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant -#define BUILTIN_LED LED_BUILTIN // Backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h b/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h index eeef95ff1..b8a33f721 100644 --- a/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h +++ b/variants/esp32s3/heltec_vision_master_t190/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 35; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_paper/pins_arduino.h b/variants/esp32s3/heltec_wireless_paper/pins_arduino.h index 3e36d98f5..886cab254 100644 --- a/variants/esp32s3/heltec_wireless_paper/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_paper/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h b/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h index 2bb44161a..0c486eebc 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_paper_v1/pins_arduino.h @@ -3,10 +3,6 @@ #include -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t KEY_BUILTIN = 0; static const uint8_t TX = 43; diff --git a/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h index 28b982012..7a1f43eee 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h index 61c319109..9fb825002 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h @@ -10,10 +10,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/m5stack_cores3/pins_arduino.h b/variants/esp32s3/m5stack_cores3/pins_arduino.h index 78e936990..ff7d35993 100644 --- a/variants/esp32s3/m5stack_cores3/pins_arduino.h +++ b/variants/esp32s3/m5stack_cores3/pins_arduino.h @@ -10,10 +10,7 @@ // Some boards have too low voltage on this pin (board design bug) // Use different pin with 3V and connect with 48 // and change this setup for the chosen pin (for example 38) -static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT + 48; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN -#define RGB_BUILTIN LED_BUILTIN +#define RGB_BUILTIN SOC_GPIO_PIN_COUNT + 48 #define RGB_BRIGHTNESS 64 static const uint8_t TX = 43; diff --git a/variants/esp32s3/mesh-tab/pins_arduino.h b/variants/esp32s3/mesh-tab/pins_arduino.h index c995f638c..d980e1a49 100644 --- a/variants/esp32s3/mesh-tab/pins_arduino.h +++ b/variants/esp32s3/mesh-tab/pins_arduino.h @@ -49,10 +49,6 @@ static const uint8_t T14 = 14; static const uint8_t VBAT_SENSE = 2; static const uint8_t VBUS_SENSE = 34; -// User LED -#define LED_BUILTIN 13 -#define BUILTIN_LED LED_BUILTIN // backward compatibility - static const uint8_t RGB_DATA = 40; // RGB_BUILTIN and RGB_BRIGHTNESS can be used in new Arduino API neopixelWrite() #define RGB_BUILTIN (RGB_DATA + SOC_GPIO_PIN_COUNT) diff --git a/variants/esp32s3/rak3312/pins_arduino.h b/variants/esp32s3/rak3312/pins_arduino.h index dc5d30d61..600c619cf 100644 --- a/variants/esp32s3/rak3312/pins_arduino.h +++ b/variants/esp32s3/rak3312/pins_arduino.h @@ -24,9 +24,6 @@ static const uint8_t SCK = 13; #define SPI_MISO (10) #define SPI_CS (12) -// LEDs -#define LED_BUILTIN LED_GREEN - #ifdef _VARIANT_RAK3112_ /* * Serial interfaces diff --git a/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h index 15a26e991..b51cd214e 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h @@ -22,7 +22,4 @@ static const uint8_t SCK = 13; #define SPI_MISO (10) #define SPI_CS (12) -// LEDs -#define LED_BUILTIN LED_GREEN - #endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h b/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h index 300f0e0f5..88c233491 100644 --- a/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h +++ b/variants/esp32s3/seeed-sensecap-indicator/pins_arduino.h @@ -3,8 +3,6 @@ #include -// static const uint8_t LED_BUILTIN = -1; - // static const uint8_t TX = 43; // static const uint8_t RX = 44; diff --git a/variants/esp32s3/t-deck/pins_arduino.h b/variants/esp32s3/t-deck/pins_arduino.h index cb429d776..c358b988e 100644 --- a/variants/esp32s3/t-deck/pins_arduino.h +++ b/variants/esp32s3/t-deck/pins_arduino.h @@ -6,8 +6,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -// static const uint8_t LED_BUILTIN = -1; - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/t-watch-s3/pins_arduino.h b/variants/esp32s3/t-watch-s3/pins_arduino.h index 35f0e933e..f4585ace8 100644 --- a/variants/esp32s3/t-watch-s3/pins_arduino.h +++ b/variants/esp32s3/t-watch-s3/pins_arduino.h @@ -3,8 +3,6 @@ #include -// static const uint8_t LED_BUILTIN = -1; - // static const uint8_t TX = 43; // static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/internal/pins_arduino.h b/variants/esp32s3/tracksenger/internal/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/internal/pins_arduino.h +++ b/variants/esp32s3/tracksenger/internal/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/lcd/pins_arduino.h b/variants/esp32s3/tracksenger/lcd/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/lcd/pins_arduino.h +++ b/variants/esp32s3/tracksenger/lcd/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/tracksenger/oled/pins_arduino.h b/variants/esp32s3/tracksenger/oled/pins_arduino.h index 1052af961..93fd5d9c2 100644 --- a/variants/esp32s3/tracksenger/oled/pins_arduino.h +++ b/variants/esp32s3/tracksenger/oled/pins_arduino.h @@ -11,10 +11,6 @@ #define USB_VID 0x303a #define USB_PID 0x1001 -static const uint8_t LED_BUILTIN = 18; -#define BUILTIN_LED LED_BUILTIN // backward compatibility -#define LED_BUILTIN LED_BUILTIN - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/esp32s3/unphone/pins_arduino.h b/variants/esp32s3/unphone/pins_arduino.h index 74067359f..8a62e3d42 100644 --- a/variants/esp32s3/unphone/pins_arduino.h +++ b/variants/esp32s3/unphone/pins_arduino.h @@ -6,9 +6,6 @@ #define USB_VID 0x16D0 #define USB_PID 0x1178 -#define LED_BUILTIN 13 -#define BUILTIN_LED LED_BUILTIN // backward compatibility - static const uint8_t TX = 43; static const uint8_t RX = 44; diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h index 2318450eb..d120e485c 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h @@ -50,7 +50,6 @@ extern "C" { #define RGBLED_BLUE (0 + 12) // Blue of RGB P0.12 #define RGBLED_CA // comment out this line if you have a common cathode type, as defined use common anode logic -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index 6f7056d27..c36211a63 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -55,7 +55,6 @@ extern "C" { #define LED_RED PIN_LED3 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit // LED灯亮时的状态 #define PIN_BUZZER (0 + 6) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index decdd6ed0..acbcf6ae5 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -58,7 +58,6 @@ extern "C" { #define LED_BLUE 37 #define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED -#define LED_BUILTIN -1 #define LED_STATE_ON LOW #define LED_STATE_OFF HIGH diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h index 1aff32838..0d905c2c1 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h @@ -40,7 +40,6 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define LED_BUILTIN -1 #define LED_BLUE -1 #define PIN_LED2 (32 + 9) #define LED_PAIRING (13) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index e46391207..2461d73d2 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -40,7 +40,6 @@ extern "C" { #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_CHARGE (12) #define LED_PAIRING (7) diff --git a/variants/nrf52840/ME25LS01-4Y10TD/variant.h b/variants/nrf52840/ME25LS01-4Y10TD/variant.h index e772069da..a920b02e5 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h index 797394ce6..683669160 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/MS24SF1/variant.h b/variants/nrf52840/MS24SF1/variant.h index d26dcebc2..a71be99fd 100644 --- a/variants/nrf52840/MS24SF1/variant.h +++ b/variants/nrf52840/MS24SF1/variant.h @@ -51,7 +51,6 @@ extern "C" { #define PIN_LED1 (-1) #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.h b/variants/nrf52840/MakePython_nRF52840_eink/variant.h index 00c8dc199..91ca39d1d 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.h @@ -29,7 +29,6 @@ extern "C" { #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.h b/variants/nrf52840/MakePython_nRF52840_oled/variant.h index 28d941171..286c5c251 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.h @@ -29,7 +29,6 @@ extern "C" { #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/TWC_mesh_v4/variant.h b/variants/nrf52840/TWC_mesh_v4/variant.h index 6a6f541e6..5b4623210 100644 --- a/variants/nrf52840/TWC_mesh_v4/variant.h +++ b/variants/nrf52840/TWC_mesh_v4/variant.h @@ -34,7 +34,6 @@ extern "C" { // #define PIN_LED1 (32 + 9) Green // #define PIN_LED1 (0 + 12) Blue -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 537f4e26c..4d388c376 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -52,7 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED3 #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h index 63af1fe79..8e10141f5 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/variant.h @@ -81,7 +81,6 @@ NRF52 PRO MICRO PIN ASSIGNMENT // LED #define PIN_LED1 (0 + 15) // P0.15 -#define LED_BUILTIN PIN_LED1 // Actually red #define LED_BLUE PIN_LED1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h index 277377d71..407bc1541 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h @@ -38,7 +38,6 @@ extern "C" { #define PIN_LED PIN_LED1 #define LED_PWR (PINS_COUNT) -#define LED_BUILTIN PIN_LED #define LED_STATE_ON 1 // State when LED is lit // XIAO Wio-SX1262 Shield User button diff --git a/variants/nrf52840/dls_Minimesh_Lite/variant.h b/variants/nrf52840/dls_Minimesh_Lite/variant.h index f5163619b..32c16f06d 100644 --- a/variants/nrf52840/dls_Minimesh_Lite/variant.h +++ b/variants/nrf52840/dls_Minimesh_Lite/variant.h @@ -44,7 +44,6 @@ extern "C" { // LED #define PIN_LED1 (0 + 15) -#define LED_BUILTIN PIN_LED1 // Actually red #define LED_BLUE PIN_LED1 #define LED_STATE_ON 1 diff --git a/variants/nrf52840/feather_diy/variant.h b/variants/nrf52840/feather_diy/variant.h index 1c0979f82..a9782a8ee 100644 --- a/variants/nrf52840/feather_diy/variant.h +++ b/variants/nrf52840/feather_diy/variant.h @@ -49,8 +49,6 @@ extern "C" { #define PIN_LED1 (32 + 15) // P1.15 3 #define PIN_LED2 (32 + 10) // P1.10 4 -#define LED_BUILTIN PIN_LED1 - #define LED_GREEN PIN_LED2 // Actually red #define LED_BLUE PIN_LED1 diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 6337ac70c..282953cd9 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h index 14170d5f3..96fa35869 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h @@ -30,7 +30,6 @@ extern "C" { #define PIN_LED1 (32 + 3) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index cf14b5e04..b3aa942ff 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -74,7 +74,6 @@ extern "C" { #define PIN_LED1 (32 + 3) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels diff --git a/variants/nrf52840/heltec_mesh_pocket/variant.h b/variants/nrf52840/heltec_mesh_pocket/variant.h index 7ec9b88ea..c80fefd58 100644 --- a/variants/nrf52840/heltec_mesh_pocket/variant.h +++ b/variants/nrf52840/heltec_mesh_pocket/variant.h @@ -26,7 +26,6 @@ extern "C" { #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_BLUE #define LED_CONN LED_BLUE #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index c80b81da4..f05668e56 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -42,7 +42,6 @@ extern "C" { #define PIN_LED1 (0 + 4) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 -#define LED_BUILTIN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit #define HAS_NEOPIXEL // Enable the use of neopixels diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index 2e252ff95..9075d0467 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -31,7 +31,6 @@ extern "C" { // LEDs #define PIN_LED1 (24) // Built in white led for status #define LED_BLUE PIN_LED1 -#define LED_BUILTIN PIN_LED1 #define LED_STATE_ON 0 // State when LED is litted #define LED_INVERTED 1 diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index 8d634ba60..d2cffd169 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -49,7 +49,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index 97536b169..e5896091b 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -52,7 +52,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) // Connected to WWAN host LED (if present) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index 8a3532b77..1ac8d9c27 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -45,7 +45,6 @@ extern "C" { #define PIN_LED1 (32 + 3) // P1.03, Green #define PIN_LED2 (32 + 4) // P1.04, Blue -#define LED_BUILTIN -1 // PIN_LED1 #define LED_BLUE PIN_LED2 #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index d8f41a68c..8c3451835 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -50,7 +50,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index b1d96ebd0..830126812 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (32 + 4) // P1.04 Controls Green LED #define PIN_LED2 (28) // P0.28 Controls Blue LED -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index f922e8a61..2fa0bd1e7 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index d4bb1a175..4cc565d98 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index 302e531d5..b0215ad28 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index c1e11bee5..574a0e68f 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index 1f8257e8e..7f1a5d112 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -29,7 +29,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index c8a2f83ae..2e8ae129c 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 51baf3ada..84793bfb3 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h index fa3e252ab..c31443371 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.h +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index a7b9290a5..a6eccc3c8 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -47,7 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index b2a1e6dff..7473fd073 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -23,7 +23,6 @@ #define PIN_LED1 (12) // LED P1.15 #define PIN_LED2 (11) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/seeed_wio_tracker_L1/variant.h b/variants/nrf52840/seeed_wio_tracker_L1/variant.h index b62b65161..70b35336b 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1/variant.h @@ -23,7 +23,6 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index ae20f3c36..87aa5bb80 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -23,7 +23,6 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_GREEN PIN_LED1 diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index 0844595da..e4305a57a 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -69,8 +69,6 @@ static const uint8_t A5 = PIN_A5; #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define LED_BUILTIN LED_RED // LED_BUILTIN is used by framework-arduinoadafruitnrf52 to indicate flash writes - #define LED_PWR LED_RED #define USER_LED LED_BLUE diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 12dcb8949..de93b10f4 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -52,7 +52,6 @@ extern "C" { #define BLE_LED LED_BLUE #define BLE_LED_INVERTED 1 -#define LED_BUILTIN LED_GREEN #define LED_CONN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/t-echo-plus/variant.h b/variants/nrf52840/t-echo-plus/variant.h index 2d00f8c1f..7e5235902 100644 --- a/variants/nrf52840/t-echo-plus/variant.h +++ b/variants/nrf52840/t-echo-plus/variant.h @@ -25,7 +25,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE #define LED_CONN LED_GREEN #define LED_STATE_ON 0 diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 8e72fd84b..7714f5ce3 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -52,7 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_BUILTIN LED_BLUE #define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index 5b6719e12..143b7d4ad 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (0 + 24) // P0.24 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/wio-sdk-wm1110/variant.h b/variants/nrf52840/wio-sdk-wm1110/variant.h index b6e5c79df..d802d20f6 100644 --- a/variants/nrf52840/wio-sdk-wm1110/variant.h +++ b/variants/nrf52840/wio-sdk-wm1110/variant.h @@ -58,8 +58,6 @@ extern "C" { #define PIN_LED1 (0 + 13) // P0.13 #define PIN_LED2 (0 + 14) // P0.14 -#define LED_BUILTIN PIN_LED1 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // Actually red diff --git a/variants/nrf52840/wio-t1000-s/variant.h b/variants/nrf52840/wio-t1000-s/variant.h index 02f8a20b2..3b8103d85 100644 --- a/variants/nrf52840/wio-t1000-s/variant.h +++ b/variants/nrf52840/wio-t1000-s/variant.h @@ -48,7 +48,6 @@ extern "C" { #define PIN_LED1 (0 + 24) // P0.24 #define LED_PIN PIN_LED1 -#define LED_BUILTIN -1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/wio-tracker-wm1110/variant.h b/variants/nrf52840/wio-tracker-wm1110/variant.h index 807ca8dbb..145750da5 100644 --- a/variants/nrf52840/wio-tracker-wm1110/variant.h +++ b/variants/nrf52840/wio-tracker-wm1110/variant.h @@ -54,8 +54,6 @@ extern "C" { #define PIN_LED1 (0 + 6) // P0.06 #define PIN_LED2 (PINS_COUNT) // P0.14 -#define LED_BUILTIN PIN_LED1 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h index ac472c07e..5e7311413 100644 --- a/variants/rp2040/challenger_2040_lora/pins_arduino.h +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -45,8 +45,6 @@ #define SPI_HOWMANY (2u) #define WIRE_HOWMANY (1u) -#define LED_BUILTIN PIN_LED - static const uint8_t D0 = (16u); static const uint8_t D1 = (17u); static const uint8_t D2 = (20u); diff --git a/variants/rp2040/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h index 0e2808b19..95ee308bc 100644 --- a/variants/rp2040/rak11310/pins_arduino.h +++ b/variants/rp2040/rak11310/pins_arduino.h @@ -26,7 +26,6 @@ static const uint8_t A3 = PIN_A3; #define PIN_LED (23u) #define PIN_LED1 PIN_LED #define PIN_LED2 (24u) -#define LED_BUILTIN PIN_LED #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index 2400d56a7..36c77d209 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -11,7 +11,7 @@ #define I2C_SCL1 3 #define LED_CONN PIN_LED2 -#define LED_PIN LED_BUILTIN +#define LED_PIN PIN_LED #define ledOff(pin) pinMode(pin, INPUT) #define BUTTON_PIN 9 diff --git a/variants/rp2040/rpipicow/platformio.ini b/variants/rp2040/rpipicow/platformio.ini index 99e02a1aa..9b4b29a5b 100644 --- a/variants/rp2040/rpipicow/platformio.ini +++ b/variants/rp2040/rpipicow/platformio.ini @@ -22,6 +22,7 @@ build_flags = -D HW_SPI1_DEVICE -D HAS_UDP_MULTICAST=1 -fexceptions # for exception handling in MQTT + -ULED_BUILTIN build_src_filter = ${rp2040_base.build_src_filter} + lib_deps = ${rp2040_base.lib_deps} diff --git a/variants/rp2040/rpipicow/variant.h b/variants/rp2040/rpipicow/variant.h index 24da8f932..fe94e615d 100644 --- a/variants/rp2040/rpipicow/variant.h +++ b/variants/rp2040/rpipicow/variant.h @@ -19,7 +19,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN LED_BUILTIN +#define LED_PIN PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/senselora_rp2040/pins_arduino.h b/variants/rp2040/senselora_rp2040/pins_arduino.h index bb0ee637e..61705c8d9 100644 --- a/variants/rp2040/senselora_rp2040/pins_arduino.h +++ b/variants/rp2040/senselora_rp2040/pins_arduino.h @@ -13,7 +13,6 @@ static const uint8_t A3 = PIN_A3; // LEDs #define PIN_LED (23u) #define PIN_LED1 PIN_LED -#define LED_BUILTIN PIN_LED #define ADC_RESOLUTION 12 diff --git a/variants/stm32/wio-e5/variant.h b/variants/stm32/wio-e5/variant.h index a312b31bd..2b20eb2a6 100644 --- a/variants/stm32/wio-e5/variant.h +++ b/variants/stm32/wio-e5/variant.h @@ -19,9 +19,4 @@ Do not expect a working Meshtastic device with this target. #define WIO_E5 -#if (defined(LED_BUILTIN) && LED_BUILTIN == PNUM_NOT_DEFINED) -#undef LED_BUILTIN -#define LED_BUILTIN (LED_PIN) -#endif - #endif From d44ceb6eb2d6cec2369e398a27ed286be4284242 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 28 Jan 2026 17:17:35 -0600 Subject: [PATCH 043/223] Fix NimBLE deinit null check --- src/nimble/NimbleBluetooth.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index fc1f27ea2..2a59c0aab 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -686,6 +686,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks #ifdef NIMBLE_TWO if (ble->isDeInit) return; +#else + if (nimbleBluetooth && nimbleBluetooth->isDeInit) + return; #endif meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); From d7d6fe7f0f1306d2c53e8782b4bc1da7c59e6fcf Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Thu, 29 Jan 2026 00:18:42 +0100 Subject: [PATCH 044/223] Avoid short-circuit evaluation issues in Telemetry (#9467) * Make sensors in telemetry non-definitory Co-authored-by: Ben Meadors --- .../Telemetry/EnvironmentTelemetry.cpp | 23 +++++++++++++------ src/modules/Telemetry/HealthTelemetry.cpp | 9 +++++--- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 86a8606c2..140c2c17e 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -529,37 +529,46 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m) { - bool valid = true; + bool valid = false; bool hasSensor = false; + // getMetrics() doesn't always get evaluated because of + // short-circuit evaluation rules in c++ + bool get_metrics; m->time = getTime(); m->which_variant = meshtastic_Telemetry_environment_metrics_tag; m->variant.environment_metrics = meshtastic_EnvironmentMetrics_init_zero; for (TelemetrySensor *sensor : sensors) { - valid = valid && sensor->getMetrics(m); + get_metrics = sensor->getMetrics(m); // avoid short-circuit evaluation rules + valid = valid || get_metrics; hasSensor = true; } #ifndef T1000X_SENSOR_EN if (ina219Sensor.hasSensor()) { - valid = valid && ina219Sensor.getMetrics(m); + get_metrics = ina219Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (ina260Sensor.hasSensor()) { - valid = valid && ina260Sensor.getMetrics(m); + get_metrics = ina260Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (ina3221Sensor.hasSensor()) { - valid = valid && ina3221Sensor.getMetrics(m); + get_metrics = ina3221Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } if (max17048Sensor.hasSensor()) { - valid = valid && max17048Sensor.getMetrics(m); + get_metrics = max17048Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } #endif #ifdef HAS_RAKPROT - valid = valid && rak9154Sensor.getMetrics(m); + get_metrics = rak9154Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; #endif return valid && hasSensor; diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index 572f0281a..bb3555062 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -168,18 +168,21 @@ bool HealthTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & bool HealthTelemetryModule::getHealthTelemetry(meshtastic_Telemetry *m) { - bool valid = true; + bool valid = false; bool hasSensor = false; + bool get_metrics; m->time = getTime(); m->which_variant = meshtastic_Telemetry_health_metrics_tag; m->variant.health_metrics = meshtastic_HealthMetrics_init_zero; if (max30102Sensor.hasSensor()) { - valid = valid && max30102Sensor.getMetrics(m); + get_metrics = max30102Sensor.getMetrics(m); + valid = valid || get_metrics; // avoid short-circuit evaluation rules hasSensor = true; } if (mlx90614Sensor.hasSensor()) { - valid = valid && mlx90614Sensor.getMetrics(m); + get_metrics = mlx90614Sensor.getMetrics(m); + valid = valid || get_metrics; hasSensor = true; } From 6ab2f02dbc3537461dbb1b39dd5cb2deabb2c726 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 17:53:12 -0600 Subject: [PATCH 045/223] re-add unintentionally removed include --- src/modules/Modules.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index c3463a07e..a24bd95b5 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,6 +1,7 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "buzz/BuzzerFeedbackThread.h" +#include "modules/StatusLEDModule.h" #include "modules/SystemCommandsModule.h" #endif #if !MESHTASTIC_EXCLUDE_PKI From df400850c1dbb3062b4091ff82fcce0583ba568d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 28 Jan 2026 18:56:57 -0600 Subject: [PATCH 046/223] Undefine LED_BUILTIN where needed --- variants/esp32/chatter2/platformio.ini | 1 + variants/esp32/diy/hydra/platformio.ini | 1 + variants/esp32/diy/v1/platformio.ini | 1 + variants/esp32/nano-g1-explorer/platformio.ini | 1 + variants/esp32/nano-g1/platformio.ini | 1 + variants/esp32/radiomaster_900_bandit/platformio.ini | 1 + variants/esp32/radiomaster_900_bandit_micro/platformio.ini | 1 + variants/esp32/radiomaster_900_bandit_nano/platformio.ini | 1 + variants/esp32/station-g1/platformio.ini | 1 + variants/esp32/tlora_v2_1_16/platformio.ini | 2 +- variants/esp32/tlora_v3_3_0_tcxo/platformio.ini | 3 ++- variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini | 1 + variants/esp32s3/heltec_sensor_hub/platformio.ini | 1 + variants/esp32s3/heltec_wsl_v3/platformio.ini | 1 + 14 files changed, 15 insertions(+), 2 deletions(-) diff --git a/variants/esp32/chatter2/platformio.ini b/variants/esp32/chatter2/platformio.ini index a14e407a1..62d23b1e6 100644 --- a/variants/esp32/chatter2/platformio.ini +++ b/variants/esp32/chatter2/platformio.ini @@ -8,6 +8,7 @@ build_flags = -I variants/esp32/chatter2 -DMESHTASTIC_EXCLUDE_WEBSERVER=1 -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -ULED_BUILTIN lib_deps = ${esp32_base.lib_deps} diff --git a/variants/esp32/diy/hydra/platformio.ini b/variants/esp32/diy/hydra/platformio.ini index 3afd17e01..f23224f0b 100644 --- a/variants/esp32/diy/hydra/platformio.ini +++ b/variants/esp32/diy/hydra/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D DIY_V1 -I variants/esp32/diy/hydra + -ULED_BUILTIN diff --git a/variants/esp32/diy/v1/platformio.ini b/variants/esp32/diy/v1/platformio.ini index 3d31fc24a..6be2bfd09 100644 --- a/variants/esp32/diy/v1/platformio.ini +++ b/variants/esp32/diy/v1/platformio.ini @@ -17,3 +17,4 @@ build_flags = -D DIY_V1 -D EBYTE_E22 -I variants/esp32/diy/v1 + -ULED_BUILTIN diff --git a/variants/esp32/nano-g1-explorer/platformio.ini b/variants/esp32/nano-g1-explorer/platformio.ini index 703bb9d09..16ecb99cc 100644 --- a/variants/esp32/nano-g1-explorer/platformio.ini +++ b/variants/esp32/nano-g1-explorer/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D NANO_G1_EXPLORER -I variants/esp32/nano-g1-explorer + -ULED_BUILTIN diff --git a/variants/esp32/nano-g1/platformio.ini b/variants/esp32/nano-g1/platformio.ini index b0ebd191c..724e008c7 100644 --- a/variants/esp32/nano-g1/platformio.ini +++ b/variants/esp32/nano-g1/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D NANO_G1 -I variants/esp32/nano-g1 + -ULED_BUILTIN diff --git a/variants/esp32/radiomaster_900_bandit/platformio.ini b/variants/esp32/radiomaster_900_bandit/platformio.ini index 6729235ed..0012f49d3 100644 --- a/variants/esp32/radiomaster_900_bandit/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit/platformio.ini @@ -9,6 +9,7 @@ build_flags = -DHAS_STK8XXX=1 -O2 -I variants/esp32/radiomaster_900_bandit + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool lib_deps = diff --git a/variants/esp32/radiomaster_900_bandit_micro/platformio.ini b/variants/esp32/radiomaster_900_bandit_micro/platformio.ini index 32e9280e1..e58d06f1e 100644 --- a/variants/esp32/radiomaster_900_bandit_micro/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit_micro/platformio.ini @@ -13,5 +13,6 @@ build_flags = -DCONFIG_DISABLE_HAL_LOCKS=1 -O2 -I variants/esp32/radiomaster_900_bandit_nano + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool diff --git a/variants/esp32/radiomaster_900_bandit_nano/platformio.ini b/variants/esp32/radiomaster_900_bandit_nano/platformio.ini index 924447ee4..7b3d187bf 100644 --- a/variants/esp32/radiomaster_900_bandit_nano/platformio.ini +++ b/variants/esp32/radiomaster_900_bandit_nano/platformio.ini @@ -16,5 +16,6 @@ build_flags = -DCONFIG_DISABLE_HAL_LOCKS=1 -O2 -I variants/esp32/radiomaster_900_bandit_nano + -ULED_BUILTIN board_build.f_cpu = 240000000L upload_protocol = esptool diff --git a/variants/esp32/station-g1/platformio.ini b/variants/esp32/station-g1/platformio.ini index ab7fcac2b..fad003b20 100644 --- a/variants/esp32/station-g1/platformio.ini +++ b/variants/esp32/station-g1/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D STATION_G1 -I variants/esp32/station-g1 + -ULED_BUILTIN diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini index d9cb8ed3b..dfdbcb152 100644 --- a/variants/esp32/tlora_v2_1_16/platformio.ini +++ b/variants/esp32/tlora_v2_1_16/platformio.ini @@ -12,7 +12,7 @@ extends = esp32_base board = ttgo-lora32-v21 board_check = true build_flags = - ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 + ${esp32_base.build_flags} -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -ULED_BUILTIN upload_speed = 115200 [env:sugarcube] diff --git a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini index 1258fd8b7..38f14ffc5 100644 --- a/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini +++ b/variants/esp32/tlora_v3_3_0_tcxo/platformio.ini @@ -6,4 +6,5 @@ build_flags = -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -D LORA_TCXO_GPIO=12 - -D BUTTON_PIN=0 \ No newline at end of file + -D BUTTON_PIN=0 + -ULED_BUILTIN \ No newline at end of file diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini index 0bb21581a..6dd828433 100644 --- a/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini +++ b/variants/esp32s3/heltec_capsule_sensor_v3/platformio.ini @@ -6,4 +6,5 @@ board_build.partitions = default_8MB.csv build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_capsule_sensor_v3 -D HELTEC_CAPSULE_SENSOR_V3 + -ULED_BUILTIN ;-D DEBUG_DISABLED ; uncomment this line to disable DEBUG output diff --git a/variants/esp32s3/heltec_sensor_hub/platformio.ini b/variants/esp32s3/heltec_sensor_hub/platformio.ini index ded0c22fe..3f93c7ad2 100644 --- a/variants/esp32s3/heltec_sensor_hub/platformio.ini +++ b/variants/esp32s3/heltec_sensor_hub/platformio.ini @@ -7,6 +7,7 @@ build_flags = ${esp32s3_base.build_flags} -I variants/esp32s3/heltec_sensor_hub -D HELTEC_SENSOR_HUB + -ULED_BUILTIN lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel diff --git a/variants/esp32s3/heltec_wsl_v3/platformio.ini b/variants/esp32s3/heltec_wsl_v3/platformio.ini index 0903a6bc7..873300c3c 100644 --- a/variants/esp32s3/heltec_wsl_v3/platformio.ini +++ b/variants/esp32s3/heltec_wsl_v3/platformio.ini @@ -17,3 +17,4 @@ build_flags = ${esp32s3_base.build_flags} -D HELTEC_WSL_V3 -I variants/esp32s3/heltec_wsl_v3 + -ULED_BUILTIN From b2f2f6b305e9cdc66fdb129fd6795b642fd96c2f Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:41:27 +0800 Subject: [PATCH 047/223] Add a watchdog module to meshsolar. (#9337) * add watchdog module * Restore the code in power.h --------- Co-authored-by: Ben Meadors --- src/modules/Modules.cpp | 6 +++ src/watchdog/watchdogThread.cpp | 37 +++++++++++++++++++ src/watchdog/watchdogThread.h | 19 ++++++++++ variants/nrf52840/heltec_mesh_solar/variant.h | 20 ++++++---- 4 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 src/watchdog/watchdogThread.cpp create mode 100644 src/watchdog/watchdogThread.h diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index a24bd95b5..e8da8e983 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -91,6 +91,9 @@ #include "modules/DropzoneModule.h" #endif +#if defined(HAS_HARDWARE_WATCHDOG) +#include "watchdog/watchdogThread.h" +#endif /** * Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else) */ @@ -229,6 +232,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS if (moduleConfig.has_range_test && moduleConfig.range_test.enabled) new RangeTestModule(); +#endif +#if defined(HAS_HARDWARE_WATCHDOG) + watchdogThread = new WatchdogThread(); #endif // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // acks diff --git a/src/watchdog/watchdogThread.cpp b/src/watchdog/watchdogThread.cpp new file mode 100644 index 000000000..ff329f62b --- /dev/null +++ b/src/watchdog/watchdogThread.cpp @@ -0,0 +1,37 @@ +#include "watchdogThread.h" +#include "configuration.h" + +#ifdef HAS_HARDWARE_WATCHDOG +WatchdogThread *watchdogThread; + +WatchdogThread::WatchdogThread() : OSThread("Watchdog") +{ + setup(); +} + +void WatchdogThread::feedDog(void) +{ + digitalWrite(HARDWARE_WATCHDOG_DONE, HIGH); + delay(1); + digitalWrite(HARDWARE_WATCHDOG_DONE, LOW); +} + +int32_t WatchdogThread::runOnce() +{ + LOG_DEBUG("Feeding hardware watchdog"); + feedDog(); + return HARDWARE_WATCHDOG_TIMEOUT_MS; +} + +bool WatchdogThread::setup() +{ + LOG_DEBUG("init hardware watchdog"); + pinMode(HARDWARE_WATCHDOG_WAKE, INPUT); + pinMode(HARDWARE_WATCHDOG_DONE, OUTPUT); + delay(1); + digitalWrite(HARDWARE_WATCHDOG_DONE, LOW); + delay(1); + feedDog(); + return true; +} +#endif \ No newline at end of file diff --git a/src/watchdog/watchdogThread.h b/src/watchdog/watchdogThread.h new file mode 100644 index 000000000..5756afa46 --- /dev/null +++ b/src/watchdog/watchdogThread.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "concurrency/OSThread.h" + +#ifdef HAS_HARDWARE_WATCHDOG +class WatchdogThread : private concurrency::OSThread +{ + public: + + WatchdogThread(); + void feedDog(void); + virtual bool setup(); + virtual int32_t runOnce() override; + +}; + +extern WatchdogThread *watchdogThread; +#endif diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index f05668e56..02cf724c3 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -39,15 +39,15 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -#define PIN_LED1 (0 + 4) // green (confirmed on 1.0 board) +#define PIN_LED1 (32 + 15) // green (confirmed on 1.0 board) #define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 #define LED_STATE_ON 0 // State when LED is lit -#define HAS_NEOPIXEL // Enable the use of neopixels -#define NEOPIXEL_COUNT 1 // How many neopixels are connected -#define NEOPIXEL_DATA (32 + 15) // gpio pin used to send data to the neopixels -#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use +// #define HAS_NEOPIXEL // Enable the use of neopixels +// #define NEOPIXEL_COUNT 1 // How many neopixels are connected +// #define NEOPIXEL_DATA (32 + 15) // gpio pin used to send data to the neopixels +// #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use /* * Buttons @@ -59,8 +59,8 @@ extern "C" { /* No longer populated on PCB */ -#define PIN_SERIAL2_RX (0 + 9) -#define PIN_SERIAL2_TX (0 + 10) +#define PIN_SERIAL2_RX (-1) +#define PIN_SERIAL2_TX (-1) /* * I2C @@ -137,6 +137,12 @@ No longer populated on PCB // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER +// Hardware watchdog +#define HAS_HARDWARE_WATCHDOG +#define HARDWARE_WATCHDOG_DONE (0 + 9) +#define HARDWARE_WATCHDOG_WAKE (0 + 10) +#define HARDWARE_WATCHDOG_TIMEOUT_MS (6*60*1000) // 6 minute watchdog + #define BQ4050_SDA_PIN (32 + 1) // I2C data line pin #define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin #define BQ4050_EMERGENCY_SHUTDOWN_PIN (32 + 3) // Emergency shutdown pin From 415686dd067f06d2f6df3e044ec20240e2126e48 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Jan 2026 05:56:19 -0600 Subject: [PATCH 048/223] Trunk --- src/watchdog/watchdogThread.cpp | 2 +- src/watchdog/watchdogThread.h | 4 +--- variants/nrf52840/heltec_mesh_solar/variant.h | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/watchdog/watchdogThread.cpp b/src/watchdog/watchdogThread.cpp index ff329f62b..3e8a5466f 100644 --- a/src/watchdog/watchdogThread.cpp +++ b/src/watchdog/watchdogThread.cpp @@ -10,7 +10,7 @@ WatchdogThread::WatchdogThread() : OSThread("Watchdog") } void WatchdogThread::feedDog(void) -{ +{ digitalWrite(HARDWARE_WATCHDOG_DONE, HIGH); delay(1); digitalWrite(HARDWARE_WATCHDOG_DONE, LOW); diff --git a/src/watchdog/watchdogThread.h b/src/watchdog/watchdogThread.h index 5756afa46..3a3830aa4 100644 --- a/src/watchdog/watchdogThread.h +++ b/src/watchdog/watchdogThread.h @@ -1,18 +1,16 @@ #pragma once -#include #include "concurrency/OSThread.h" +#include #ifdef HAS_HARDWARE_WATCHDOG class WatchdogThread : private concurrency::OSThread { public: - WatchdogThread(); void feedDog(void); virtual bool setup(); virtual int32_t runOnce() override; - }; extern WatchdogThread *watchdogThread; diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 02cf724c3..298475401 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -39,8 +39,8 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -#define PIN_LED1 (32 + 15) // green (confirmed on 1.0 board) -#define LED_BLUE PIN_LED1 // fake for bluefruit library +#define PIN_LED1 (32 + 15) // green (confirmed on 1.0 board) +#define LED_BLUE PIN_LED1 // fake for bluefruit library #define LED_GREEN PIN_LED1 #define LED_STATE_ON 0 // State when LED is lit @@ -141,7 +141,7 @@ No longer populated on PCB #define HAS_HARDWARE_WATCHDOG #define HARDWARE_WATCHDOG_DONE (0 + 9) #define HARDWARE_WATCHDOG_WAKE (0 + 10) -#define HARDWARE_WATCHDOG_TIMEOUT_MS (6*60*1000) // 6 minute watchdog +#define HARDWARE_WATCHDOG_TIMEOUT_MS (6 * 60 * 1000) // 6 minute watchdog #define BQ4050_SDA_PIN (32 + 1) // I2C data line pin #define BQ4050_SCL_PIN (32 + 0) // I2C clock line pin From 03084f6d3bbf20fcac7421f7bbff516b126e46cb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Jan 2026 06:23:52 -0600 Subject: [PATCH 049/223] PRs with needs-review still should get bot labeled --- .github/workflows/models_pr_triage.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/models_pr_triage.yml b/.github/workflows/models_pr_triage.yml index ef303c02a..d4c8509d2 100644 --- a/.github/workflows/models_pr_triage.yml +++ b/.github/workflows/models_pr_triage.yml @@ -88,9 +88,10 @@ jobs: # ───────────────────────────────────────────────────────────────────────── # Step 3: Auto-label PR type (bugfix/hardware-support/enhancement) + # Only skip for spam/ai-generated; still classify needs-review PRs # ───────────────────────────────────────────────────────────────────────── - name: Classify PR for labeling - if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') + if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.quality.outputs.response != 'spam' && steps.quality.outputs.response != 'ai-generated' uses: actions/ai-inference@v2 id: classify continue-on-error: true From b18742c2112309199150f39cb5a85a718290690e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 06:25:07 -0600 Subject: [PATCH 050/223] Update libch341-spi-userspace digest to af9bc27 (#9472) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/native/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index b86420291..f99ee9a6f 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -29,7 +29,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 # 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 + https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main From 334a4f04cdd236214d1cd6cceb75a2c6b5a4bfd4 Mon Sep 17 00:00:00 2001 From: "Justin E. Mann" Date: Thu, 29 Jan 2026 05:29:15 -0700 Subject: [PATCH 051/223] Fix logic for rak12035 sensor default config and improve messaging (#9414) * better logic to check if the RAK12035 soil sensor is calibrated, better log messaging if either of the default values were used. * . * changes to how default calibration is done and a message it the default calibration is used pointing to the actual calibration sketch so the user can find it and use it to improve accuracy. --------- Co-authored-by: Ben Meadors --- .../Telemetry/Sensor/RAK12035Sensor.cpp | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp index 626cc0e87..4f3150b25 100644 --- a/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp +++ b/src/modules/Telemetry/Sensor/RAK12035Sensor.cpp @@ -26,7 +26,7 @@ bool RAK12035Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) sensor.get_sensor_version(&data); if (data != 0) { LOG_INFO("Init sensor: %s", sensorName); - LOG_INFO("RAK12035Sensor Init Succeed \nSensor1 Firmware version: %i, Sensor Name: %s", data, sensorName); + LOG_INFO("RAK12035Sensor Init Succeed \nSensor Firmware version: %i, Sensor Name: %s", data, sensorName); status = true; sensor.sensor_sleep(); RESTORE_3V3_POWER(); @@ -49,33 +49,39 @@ void RAK12035Sensor::setup() // TODO:: Check for and run calibration check for up to 2 additional sensors if present. uint16_t zero_val = 0; uint16_t hundred_val = 0; - uint16_t default_zero_val = 550; - uint16_t default_hundred_val = 420; + const uint16_t default_zero_val = 510; + const uint16_t default_hundred_val = 390; + sensor.sensor_on(); + sensor.begin(); delay(200); sensor.get_dry_cal(&zero_val); + delay(200); sensor.get_wet_cal(&hundred_val); delay(200); - if (zero_val == 0 || zero_val <= hundred_val) { - LOG_INFO("Dry calibration value is %d", zero_val); - LOG_INFO("Wet calibration value is %d", hundred_val); - LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: " - "https://github.com/RAKWireless/RAK12035_SoilMoisture."); - LOG_INFO("For now, setting default calibration value for Dry Calibration: %d", default_zero_val); + + bool calibrationReset = false; + + if (zero_val == 0) { + LOG_INFO("Dry calibration not set, using default: %d", default_zero_val); sensor.set_dry_cal(default_zero_val); - sensor.get_dry_cal(&zero_val); - LOG_INFO("Dry calibration reset complete. New value is %d", zero_val); + delay(200); + zero_val = default_zero_val; + calibrationReset = true; } if (hundred_val == 0 || hundred_val >= zero_val) { - LOG_INFO("Dry calibration value is %d", zero_val); - LOG_INFO("Wet calibration value is %d", hundred_val); - LOG_INFO("This does not make sense. You can recalibrate this sensor using the calibration sketch included here: " - "https://github.com/RAKWireless/RAK12035_SoilMoisture."); - LOG_INFO("For now, setting default calibration value for Wet Calibration: %d", default_hundred_val); + LOG_INFO("Wet calibration not set, using default: %d", default_hundred_val); sensor.set_wet_cal(default_hundred_val); - sensor.get_wet_cal(&hundred_val); - LOG_INFO("Wet calibration reset complete. New value is %d", hundred_val); + delay(200); + hundred_val = default_hundred_val; + calibrationReset = true; } + if (calibrationReset) { + LOG_INFO("Default calibration values applied. Consider running the calibration sketch for better accuracy: " + "https://github.com/RAKWireless/RAK12035_SoilMoisture"); + } + + LOG_INFO("Dry calibration value: %d, Wet calibration value: %d", zero_val, hundred_val); sensor.sensor_sleep(); RESTORE_3V3_POWER(); delay(200); From 31bf51b3f2c72627d67499b2d8b942cbe8372da2 Mon Sep 17 00:00:00 2001 From: treysis Date: Thu, 29 Jan 2026 13:38:49 +0100 Subject: [PATCH 052/223] Add support for the hardware buttons on Bluetooth Nugget device (#9468) Co-authored-by: Ben Meadors --- variants/esp32s3/nugget_s3_lora/variant.h | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/variants/esp32s3/nugget_s3_lora/variant.h b/variants/esp32s3/nugget_s3_lora/variant.h index 8e6057d5b..1354d0837 100644 --- a/variants/esp32s3/nugget_s3_lora/variant.h +++ b/variants/esp32s3/nugget_s3_lora/variant.h @@ -12,7 +12,7 @@ #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use // Button A (44), B (43), R (12), U (13), L (11), D (18) -#define BUTTON_PIN 44 // If defined, this will be used for user button presses +#define BUTTON_PIN 43 // If defined, this will be used for user button presses #define BUTTON_NEED_PULLUP #define USE_RF95 @@ -20,8 +20,19 @@ #define LORA_MISO 7 #define LORA_MOSI 8 #define LORA_CS 9 -#define LORA_DIO0 16 // a No connect on the SX1262 module +#define LORA_DIO0 16 #define LORA_RESET 4 #define LORA_DIO1 RADIOLIB_NC -#define LORA_DIO2 RADIOLIB_NC \ No newline at end of file +#define LORA_DIO2 RADIOLIB_NC + +// jk, its not really a trackball but we're gonna pretend! +#define HAS_TRACKBALL 1 +#define TB_UP 13 +#define TB_DOWN 18 +#define TB_LEFT 11 +#define TB_RIGHT 12 +#define TB_PRESS 44 // BUTTON_PIN +#define TB_DIRECTION FALLING + +#define ENABLE_AMBIENTLIGHTING From 1f7ed6888a2cd7dcf058958c73ef2fc3d1eadccb Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Thu, 29 Jan 2026 20:43:48 +0800 Subject: [PATCH 053/223] feat(stm32): Add Milesight GS301 Bathroom Odor Detector (#9359) - STM32WLE5CCU6 - NFC (unsupported): NXP NT3H2211W0FTTJ (NTAG I2C plus: NFC Forum T2T with I2C interface, password protection and energy harvesting) - Sensor (unsupported): Analog ADuCM355 (SHTC3 is connected to ADuCM355 and not directly accessible) - Bicolor LED - User button (presently not functional in STM32 variants) The definitions for sensor voltage control are present but commented out to save power, due to lack of sensor support. Powered by 4x 4000mAh RAMWAY ER18505 Li-SOCl2 batteries. Flashing: 1. Power down device (remove batteries) 2. Connect USB-UART to J1 (USART2), pinout is below, do not connect +3V3 pin yet 3. Short BOOT pins next to J1 4. Connect +3V3 pin or insert batteries while BOOT pins are shorted 5. Use STM32CubeProgrammer, connect by UART mode 6. Load firmware .hex and download J1 (USART2); Molex Picoblade (P=1.25mm * 4) 1. +3V3 2. PA3_USART2_RX_J1 3. PA2_USART2_TX_J1 4. GND Signed-off-by: Andrew Yong Co-authored-by: Ben Meadors --- variants/stm32/milesight_gs301/platformio.ini | 21 ++++++++++ variants/stm32/milesight_gs301/rfswitch.h | 7 ++++ variants/stm32/milesight_gs301/variant.h | 41 +++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 variants/stm32/milesight_gs301/platformio.ini create mode 100644 variants/stm32/milesight_gs301/rfswitch.h create mode 100644 variants/stm32/milesight_gs301/variant.h diff --git a/variants/stm32/milesight_gs301/platformio.ini b/variants/stm32/milesight_gs301/platformio.ini new file mode 100644 index 000000000..a1d3fb34e --- /dev/null +++ b/variants/stm32/milesight_gs301/platformio.ini @@ -0,0 +1,21 @@ +; Milesight GS301 Bathroom Odor Detector +; https://www.milesight.com/iot/product/lorawan-sensor/gs301 +[env:milesight_gs301] +extends = stm32_base +board = wiscore_rak3172 ; Convenient choice as the same USART is used for programming/debug +board_level = extra +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/milesight_gs301 + -DPRIVATE_HW + -DMESHTASTIC_EXCLUDE_GPS=1 + -DMESHTASTIC_EXCLUDE_I2C=1 # Analog ADuCM355 (unsupported) so no point building support for I2C in + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 + -DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1 +build_unflags = + -DDEBUG_MUTE # We have space for debug output until sensor support is added +lib_deps = + ${stm32_base.lib_deps} + +upload_port = stlink diff --git a/variants/stm32/milesight_gs301/rfswitch.h b/variants/stm32/milesight_gs301/rfswitch.h new file mode 100644 index 000000000..d9f60038a --- /dev/null +++ b/variants/stm32/milesight_gs301/rfswitch.h @@ -0,0 +1,7 @@ +// Seems to use the same RF switch pins as RAK3172… getting Tx/Rx SNR +11dB with a nearby node +// PB8, PC13 + +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PB8, PC13, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[4] = { + {STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE}; diff --git a/variants/stm32/milesight_gs301/variant.h b/variants/stm32/milesight_gs301/variant.h new file mode 100644 index 000000000..e86e93fc4 --- /dev/null +++ b/variants/stm32/milesight_gs301/variant.h @@ -0,0 +1,41 @@ +#ifndef _VARIANT_MILESIGHT_GS301_ +#define _VARIANT_MILESIGHT_GS301_ + +#define USE_STM32WLx + +// I/O +#define LED_STATE_ON 1 +#define PIN_LED1 PA0 // Green LED +#define LED_PIN PIN_LED1 +#define PIN_LED2 PA0 // Red LED +#define USER_LED PIN_LED2 +#define BUTTON_PIN PC13 +#define BUTTON_ACTIVE_LOW true +#define BUTTON_ACTIVE_PULLUP false +#define PIN_BUZZER PA6 + +// EC Sense DGM10 Double Gas Module +// Analog ADuCM355 (unsupported); SHTC3 is connected to ADuCM355 and not directly accessible +#define PIN_WIRE_SDA PB7 +#define PIN_WIRE_SCL PB8 +// Commented out to keep sensor powered down due to lack of support +/* +#define VEXT_ENABLE PB12 // TI TPS61291DRV VSEL - set LOW before ENable for Vout = 3.3V +#define VEXT_ON_VALUE LOW +#define SENSOR_POWER_CTRL_PIN PB2 // TI TPS61291DRV EN pin +#define SENSOR_POWER_ON HIGH +#define HAS_SENSOR 1 +*/ + +#define ENABLE_HWSERIAL1 +#define PIN_SERIAL1_RX NC +#define PIN_SERIAL1_TX PB6 + +// LoRa +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +// Required to avoid Serial1 conflicts due to board definition here: +// https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U/variant_RAK3172_MODULE.h +#define RAK3172 + +#endif From 8af9e7fbdc912ba5b423e4c7885c3380f5d73b88 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Thu, 29 Jan 2026 13:44:17 +0100 Subject: [PATCH 054/223] enable long interleaving mode for LR11x0 and SX128x (#9399) Using long interleaving is not a breaking change, the receiver node is able to use the lora header to know if LI encoding is used or not and will decode LI packets correctly. However the problem is SX127x and other first generation LoRa IP which do not support LI at all, for theses it is a breaking change. HOWEVER due to the sync word bug the LR11x0 already can't talk with SX127x, so if we enable LI on theses no one would be able to tell. Same for SX128x altho this is because it works on 2.4Ghz which is incompatible with SX127x. Co-authored-by: Ben Meadors --- src/mesh/LR11x0Interface.cpp | 2 +- src/mesh/SX128xInterface.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index a8a2780ed..7c73b56cd 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -170,7 +170,7 @@ template bool LR11x0Interface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - err = lora.setCodingRate(cr); + err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 3ab63df3d..9fcedfe49 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -126,7 +126,7 @@ template bool SX128xInterface::reconfigure() if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); - err = lora.setCodingRate(cr); + err = lora.setCodingRate(cr, cr != 7); // use long interleaving except if CR is 4/7 which doesn't support it if (err != RADIOLIB_ERR_NONE) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING); From 61b39acc7d1d2c15317b2100c960df1d5e69b3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Mista?= Date: Thu, 29 Jan 2026 17:06:58 +0100 Subject: [PATCH 055/223] Add initial Nix shell (#8530) * Add initial Nix shell * Update flake.nix Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .envrc | 1 + .gitignore | 3 +++ flake.lock | 44 ++++++++++++++++++++++++++++++++++++ flake.nix | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ shell.nix | 12 ++++++++++ 5 files changed, 126 insertions(+) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 shell.nix diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..65326bb6d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use nix \ No newline at end of file diff --git a/.gitignore b/.gitignore index 769603202..d6d97c6c4 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,6 @@ idf_component.yml CMakeLists.txt /sdkconfig.* .dummy/* + +# PYTHONPATH used by the Nix shell +.python3 diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..e7a4c7ff7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,44 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1766314097, + "narHash": "sha256-laJftWbghBehazn/zxVJ8NdENVgjccsWAdAqKXhErrM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "306ea70f9eb0fb4e040f8540e2deab32ed7e2055", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..1af493c6d --- /dev/null +++ b/flake.nix @@ -0,0 +1,66 @@ +{ + description = "Nix flake to compile Meshtastic firmware"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + # Shim to make flake.nix work with stable Nix. + flake-compat = { + url = "github:NixOS/flake-compat"; + flake = false; + }; + }; + + outputs = + inputs: + let + lib = inputs.nixpkgs.lib; + + forAllSystems = + fn: + lib.genAttrs lib.systems.flakeExposed ( + system: + fn { + pkgs = import inputs.nixpkgs { + inherit system; + }; + inherit system; + } + ); + in + { + devShells = forAllSystems ( + { pkgs, ... }: + let + python3 = pkgs.python312.withPackages ( + ps: with ps; [ + google + ] + ); + in + { + default = pkgs.mkShell { + buildInputs = with pkgs; [ + python3 + platformio + ]; + + shellHook = '' + # Set up PlatformIO to use a local core directory. + export PLATFORMIO_CORE_DIR=$PWD/.platformio + # Tell pip to put packages into $PIP_PREFIX instead of the usual + # location. This is especially necessary under NixOS to avoid having + # pip trying to write to the read-only Nix store. For more info, + # see https://wiki.nixos.org/wiki/Python + export PIP_PREFIX=$PWD/.python3 + export PYTHONPATH="$PIP_PREFIX/${python3.sitePackages}" + export PATH="$PIP_PREFIX/bin:$PATH" + # Avoids reproducibility issues with some Python packages + # See https://nixos.org/manual/nixpkgs/stable/#python-setup.py-bdist_wheel-cannot-create-.whl + unset SOURCE_DATE_EPOCH + ''; + }; + } + ); + }; +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..692cd4df8 --- /dev/null +++ b/shell.nix @@ -0,0 +1,12 @@ +(import ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + nodeName = lock.nodes.root.inputs.flake-compat; + in + fetchTarball { + url = + lock.nodes.${nodeName}.locked.url + or "https://github.com/NixOS/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz"; + sha256 = lock.nodes.${nodeName}.locked.narHash; + } +) { src = ./.; }).shellNix From 45fbc0f9d330bced8d23b1f97e9d3d8191f2538c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Jan 2026 10:58:06 -0600 Subject: [PATCH 056/223] Remove stale variant.h defines (#9470) * Remove noop CANNED_MESSAGE_MODULE_ENABLE define * Remove over-eager warning removal * Remove unused LED_CONN * Dead defines removal * Rename oddball LED pin name * Rename second oddball LED pin name * Remove another dead define --- src/input/SerialKeyboard.cpp | 1 - src/modules/CannedMessageModule.cpp | 3 +-- src/modules/CannedMessageModule.h | 4 ---- src/modules/StatusLEDModule.cpp | 1 - src/sleep.cpp | 1 - variants/esp32/chatter2/variant.h | 1 - variants/esp32/m5stack_core/platformio.ini | 1 - variants/esp32/tbeam/variant.h | 1 - variants/esp32/trackerd/variant.h | 1 - variants/esp32s3/hackaday-communicator/variant.h | 12 ------------ variants/esp32s3/picomputer-s3/variant.h | 2 -- variants/esp32s3/rak3312/variant.h | 1 - variants/esp32s3/rak_wismesh_tap_v2/variant.h | 3 --- variants/esp32s3/t-deck-pro/variant.h | 1 - variants/esp32s3/t-deck/variant.h | 1 - variants/esp32s3/tlora-pager/variant.h | 1 - variants/esp32s3/tracksenger/internal/variant.h | 1 - variants/esp32s3/tracksenger/lcd/variant.h | 1 - variants/esp32s3/tracksenger/oled/variant.h | 1 - variants/native/portduino-buildroot/variant.h | 1 - variants/native/portduino/variant.h | 1 - .../nrf52840/Dongle_nRF52840-pca10059-v1/variant.h | 2 -- variants/nrf52840/ELECROW-ThinkNode-M1/variant.h | 3 --- variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp | 12 ++++++------ variants/nrf52840/ELECROW-ThinkNode-M3/variant.h | 8 ++++---- variants/nrf52840/MakePython_nRF52840_eink/variant.h | 2 -- variants/nrf52840/MakePython_nRF52840_oled/variant.h | 2 -- variants/nrf52840/TWC_mesh_v4/variant.h | 2 -- variants/nrf52840/canaryone/variant.h | 2 -- .../diy/seeed-xiao-nrf52840-wio-sx1262/variant.h | 1 - .../nrf52840/gat562_mesh_trial_tracker/variant.h | 4 ---- .../nrf52840/heltec_mesh_node_t114-inkhud/variant.h | 2 -- variants/nrf52840/heltec_mesh_node_t114/variant.h | 2 -- variants/nrf52840/heltec_mesh_pocket/variant.h | 3 --- variants/nrf52840/heltec_mesh_solar/variant.h | 2 -- variants/nrf52840/meshtiny/variant.h | 4 ---- variants/nrf52840/monteops_hw1/variant.h | 4 ---- variants/nrf52840/nano-g2-ultra/variant.h | 4 ---- variants/nrf52840/r1-neo/variant.h | 2 -- variants/nrf52840/rak2560/variant.h | 4 ---- variants/nrf52840/rak3401_1watt/variant.h | 2 -- variants/nrf52840/rak4631/variant.h | 4 ---- variants/nrf52840/rak4631_epaper/variant.h | 4 ---- variants/nrf52840/rak4631_epaper_onrxtx/variant.h | 2 -- variants/nrf52840/rak4631_eth_gw/variant.h | 4 ---- .../nrf52840/rak4631_nomadstar_meteor_pro/variant.h | 4 ---- variants/nrf52840/rak_wismeshtag/variant.h | 4 ---- variants/nrf52840/rak_wismeshtap/variant.h | 6 ------ variants/nrf52840/seeed_solar_node/variant.h | 2 -- variants/nrf52840/seeed_wio_tracker_L1/variant.h | 4 ---- .../nrf52840/seeed_wio_tracker_L1_eink/variant.h | 3 --- variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h | 1 - variants/nrf52840/t-echo-lite/variant.h | 1 - variants/nrf52840/t-echo-plus/variant.h | 2 -- variants/nrf52840/t-echo/variant.h | 3 --- variants/rp2040/rak11310/variant.h | 1 - 56 files changed, 11 insertions(+), 141 deletions(-) diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp index a5d2c614f..8037b0d57 100644 --- a/src/input/SerialKeyboard.cpp +++ b/src/input/SerialKeyboard.cpp @@ -5,7 +5,6 @@ SerialKeyboard *globalSerialKeyboard = nullptr; #ifdef INPUTBROKER_SERIAL_TYPE -#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file #if INPUTBROKER_SERIAL_TYPE == 1 // It's a Chatter // 3 SHIFT level (lower case, upper case, numbers), up to 4 repeated presses, button number diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 8d1ba6346..7d7b3cdb1 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -130,8 +130,7 @@ CannedMessageModule::CannedMessageModule() : SinglePortModule("canned", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("CannedMessage") { this->loadProtoForModule(); - if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE && - !CANNED_MESSAGE_MODULE_ENABLE) { + if ((this->splitConfiguredMessages() <= 0) && (cardkb_found.address == 0x00) && !INPUTBROKER_MATRIX_TYPE) { LOG_INFO("CannedMessageModule: No messages are configured. Module is disabled"); this->runState = CANNED_MESSAGE_RUN_STATE_DISABLED; disable(); diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h index 3d7c09d87..65715dd22 100644 --- a/src/modules/CannedMessageModule.h +++ b/src/modules/CannedMessageModule.h @@ -27,10 +27,6 @@ enum CannedMessageModuleIconType { shift, backspace, space, enter }; #define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50 #define CANNED_MESSAGE_MODULE_MESSAGES_SIZE 800 -#ifndef CANNED_MESSAGE_MODULE_ENABLE -#define CANNED_MESSAGE_MODULE_ENABLE 0 -#endif - // ============================ // Data Structures // ============================ diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index fed035513..e7a405bdf 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -130,7 +130,6 @@ int32_t StatusLEDModule::runOnce() #ifdef LED_CHARGE digitalWrite(LED_CHARGE, CHARGE_LED_state); #endif - // digitalWrite(green_LED_PIN, LED_STATE_OFF); #ifdef LED_PAIRING digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 756582c74..8b30a5352 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -241,7 +241,6 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN #ifdef PIN_POWER_EN digitalWrite(PIN_POWER_EN, LOW); pinMode(PIN_POWER_EN, INPUT); // power off peripherals - // pinMode(PIN_POWER_EN1, INPUT_PULLDOWN); #endif #ifdef RAK_WISMESH_TAP_V2 diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index abcb1ce4d..28ce64f91 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -98,7 +98,6 @@ #define KB_LOAD 21 // load values from the switch and store in shift register #define KB_CLK 22 // clock pin for serial data out #define KB_DATA 23 // data pin -#define CANNED_MESSAGE_MODULE_ENABLE 1 ///////////////////////////////////////////////////////////////////////////////// // // diff --git a/variants/esp32/m5stack_core/platformio.ini b/variants/esp32/m5stack_core/platformio.ini index 5e41d5f2b..8fbbae895 100644 --- a/variants/esp32/m5stack_core/platformio.ini +++ b/variants/esp32/m5stack_core/platformio.ini @@ -30,7 +30,6 @@ build_flags = -DTFT_BL=32 -DSPI_FREQUENCY=40000000 -DSPI_READ_FREQUENCY=16000000 - -DDISABLE_ALL_LIBRARY_WARNINGS lib_ignore = m5stack-core lib_deps = diff --git a/variants/esp32/tbeam/variant.h b/variants/esp32/tbeam/variant.h index 2d144a888..2c1e61c49 100644 --- a/variants/esp32/tbeam/variant.h +++ b/variants/esp32/tbeam/variant.h @@ -57,7 +57,6 @@ #ifndef TOUCH_IRQ #define TOUCH_IRQ -1 #endif -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 #define ST7796_NSS 25 diff --git a/variants/esp32/trackerd/variant.h b/variants/esp32/trackerd/variant.h index c4dfb9e93..0996e85ac 100644 --- a/variants/esp32/trackerd/variant.h +++ b/variants/esp32/trackerd/variant.h @@ -10,7 +10,6 @@ #define LED_PIN 13 // 13 red, 2 blue, 15 red -// #define HAS_BUTTON 0 #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/hackaday-communicator/variant.h b/variants/esp32s3/hackaday-communicator/variant.h index a127f548f..7e048d72c 100644 --- a/variants/esp32s3/hackaday-communicator/variant.h +++ b/variants/esp32s3/hackaday-communicator/variant.h @@ -16,22 +16,12 @@ #define SLEEP_TIME 120 #define GPS_DEFAULT_NOT_PRESENT 1 -// #define GPS_RX_PIN 44 -// #define GPS_TX_PIN 43 - -// #define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage -// ratio of voltage divider = 2.0 (RD2=100k, RD3=100k) -// #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. -// #define ADC_CHANNEL ADC1_GPIO4_CHANNEL // keyboard #define I2C_SDA 47 // I2C pins for this board #define I2C_SCL 14 -// #define KB_POWERON -1 // must be set to HIGH -// #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 // #define KB_BL_PIN 46 // not used for now #define KB_INT 13 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define TFT_DC 39 #define TFT_CS 41 @@ -44,11 +34,9 @@ #define LORA_MOSI 3 #define LORA_CS 17 -// #define LORA_DIO0 -1 // a No connect on the SX1262 module #define LORA_RESET 18 #define LORA_DIO1 16 // SX1262 IRQ #define LORA_DIO2 15 // SX1262 BUSY -// #define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled #define SX126X_CS LORA_CS #define SX126X_DIO1 LORA_DIO1 diff --git a/variants/esp32s3/picomputer-s3/variant.h b/variants/esp32s3/picomputer-s3/variant.h index 275da1b61..7b6218f87 100644 --- a/variants/esp32s3/picomputer-s3/variant.h +++ b/variants/esp32s3/picomputer-s3/variant.h @@ -52,8 +52,6 @@ // Picomputer gets a white on black display #define TFT_MESH_OVERRIDE COLOR565(255, 255, 255) -#define CANNED_MESSAGE_MODULE_ENABLE 1 - #define INPUTBROKER_MATRIX_TYPE 1 #define KEYS_COLS \ diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index 1f8eb9e39..4d860e476 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -24,7 +24,6 @@ #define PIN_LED1 LED_GREEN #define PIN_LED2 LED_BLUE -#define LED_CONN LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index 2fc056557..f94b7ca4e 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -32,7 +32,6 @@ #define PIN_LED1 LED_GREEN #define PIN_LED2 LED_BLUE -#define LED_CONN LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) @@ -47,10 +46,8 @@ #define SPI_MISO (10) #define SPI_CS (12) -#define HAS_BUTTON 1 #define BUTTON_PIN 0 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 #define BATTERY_PIN 1 diff --git a/variants/esp32s3/t-deck-pro/variant.h b/variants/esp32s3/t-deck-pro/variant.h index 36a1310f1..d95f07f3a 100644 --- a/variants/esp32s3/t-deck-pro/variant.h +++ b/variants/esp32s3/t-deck-pro/variant.h @@ -43,7 +43,6 @@ // TCA8418 keyboard #define KB_BL_PIN 42 -#define CANNED_MESSAGE_MODULE_ENABLE 1 // microphone PCM5102A #define PCM5102A_SCK 47 diff --git a/variants/esp32s3/t-deck/variant.h b/variants/esp32s3/t-deck/variant.h index ab5b74870..5d885579a 100644 --- a/variants/esp32s3/t-deck/variant.h +++ b/variants/esp32s3/t-deck/variant.h @@ -61,7 +61,6 @@ #define KB_POWERON 10 // must be set to HIGH #define KB_SLAVE_ADDRESS TDECK_KB_ADDR // 0x55 #define KB_BL_PIN 46 // not used for now -#define CANNED_MESSAGE_MODULE_ENABLE 1 // trackball #define HAS_TRACKBALL 1 diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h index 565f4f580..e2d4029be 100644 --- a/variants/esp32s3/tlora-pager/variant.h +++ b/variants/esp32s3/tlora-pager/variant.h @@ -61,7 +61,6 @@ #define I2C_NO_RESCAN #define KB_BL_PIN 46 #define KB_INT 6 -#define CANNED_MESSAGE_MODULE_ENABLE 1 // audio codec ES8311 #define HAS_I2S diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h index 2287dfe0b..ba3e281c8 100644 --- a/variants/esp32s3/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -78,7 +78,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h index f42a5b19f..a9bb89d68 100644 --- a/variants/esp32s3/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -102,7 +102,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/esp32s3/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h index 85cc019c4..689864b32 100644 --- a/variants/esp32s3/tracksenger/oled/variant.h +++ b/variants/esp32s3/tracksenger/oled/variant.h @@ -79,7 +79,6 @@ // keyboard changes #define PIN_BUZZER 43 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define INPUTBROKER_MATRIX_TYPE 1 diff --git a/variants/native/portduino-buildroot/variant.h b/variants/native/portduino-buildroot/variant.h index affd83051..ac6421b14 100644 --- a/variants/native/portduino-buildroot/variant.h +++ b/variants/native/portduino-buildroot/variant.h @@ -1,6 +1,5 @@ #define HAS_SCREEN 1 #define USE_TFTDISPLAY 1 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE portduino_config.maxtophone #define MAX_NUM_NODES portduino_config.MaxNodes diff --git a/variants/native/portduino/variant.h b/variants/native/portduino/variant.h index 972443450..cba4aaedd 100644 --- a/variants/native/portduino/variant.h +++ b/variants/native/portduino/variant.h @@ -2,7 +2,6 @@ #define HAS_SCREEN 1 #endif #define USE_TFTDISPLAY 1 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE portduino_config.maxtophone #define MAX_NUM_NODES portduino_config.MaxNodes diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h index d120e485c..8baf25e87 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/variant.h @@ -50,8 +50,6 @@ extern "C" { #define RGBLED_BLUE (0 + 12) // Blue of RGB P0.12 #define RGBLED_CA // comment out this line if you have a common cathode type, as defined use common anode logic -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index c36211a63..a8c7b42b5 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -55,7 +55,6 @@ extern "C" { #define LED_RED PIN_LED3 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_CONN PIN_GREEN #define LED_STATE_ON 0 // State when LED is lit // LED灯亮时的状态 #define PIN_BUZZER (0 + 6) /* @@ -170,8 +169,6 @@ External serial flash WP25R1635FZUIL0 #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -#define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp index 9769e3edd..45a64ad3b 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.cpp @@ -37,8 +37,8 @@ void initVariant() digitalWrite(KEY_POWER, HIGH); pinMode(RGB_POWER, OUTPUT); digitalWrite(RGB_POWER, HIGH); - pinMode(green_LED_PIN, OUTPUT); - digitalWrite(green_LED_PIN, LED_STATE_OFF); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, LED_STATE_OFF); pinMode(LED_BLUE, OUTPUT); pinMode(PIN_POWER_USB, INPUT); pinMode(PIN_POWER_DONE, INPUT); @@ -63,8 +63,8 @@ void initVariant() // called from main-nrf52.cpp during the cpuDeepSleep() function void variant_shutdown() { - digitalWrite(red_LED_PIN, HIGH); - digitalWrite(green_LED_PIN, HIGH); + digitalWrite(LED_RED, HIGH); + digitalWrite(LED_GREEN, HIGH); digitalWrite(LED_BLUE, HIGH); digitalWrite(PIN_EN1, LOW); @@ -81,8 +81,8 @@ void variant_shutdown() if (pin == PIN_POWER_USB || pin == BUTTON_PIN || pin == PIN_EN1 || pin == PIN_EN2 || pin == DHT_POWER || pin == ACC_POWER || pin == Battery_POWER || pin == GPS_POWER || pin == LR1110_SPI_MISO_PIN || pin == LR1110_SPI_MOSI_PIN || pin == LR1110_SPI_SCK_PIN || pin == LR1110_SPI_NSS_PIN || pin == LR1110_BUSY_PIN || - pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == green_LED_PIN || - pin == red_LED_PIN || pin == LED_BLUE) { + pin == LR1110_NRESET_PIN || pin == LR1110_IRQ_PIN || pin == GPS_TX_PIN || pin == GPS_RX_PIN || pin == LED_GREEN || + pin == LED_RED || pin == LED_BLUE) { continue; } pinMode(pin, OUTPUT); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index acbcf6ae5..955a16a88 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -50,11 +50,11 @@ extern "C" { #define EEPROM_POWER 7 // LED -#define red_LED_PIN 33 -#define LED_POWER red_LED_PIN +#define LED_RED 33 +#define LED_POWER LED_RED #define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED -#define green_LED_PIN 35 -#define PIN_LED2 green_LED_PIN +#define LED_GREEN 35 +#define PIN_LED2 LED_GREEN #define LED_BLUE 37 #define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.h b/variants/nrf52840/MakePython_nRF52840_eink/variant.h index 91ca39d1d..48256e65f 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.h @@ -29,8 +29,6 @@ extern "C" { #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.h b/variants/nrf52840/MakePython_nRF52840_oled/variant.h index 286c5c251..025324037 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.h @@ -29,8 +29,6 @@ extern "C" { #define PIN_LED1 (32 + 10) // LED P1.15 #define PIN_LED2 (-1) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/TWC_mesh_v4/variant.h b/variants/nrf52840/TWC_mesh_v4/variant.h index 5b4623210..875040b5f 100644 --- a/variants/nrf52840/TWC_mesh_v4/variant.h +++ b/variants/nrf52840/TWC_mesh_v4/variant.h @@ -34,8 +34,6 @@ extern "C" { // #define PIN_LED1 (32 + 9) Green // #define PIN_LED1 (0 + 12) Blue -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/canaryone/variant.h b/variants/nrf52840/canaryone/variant.h index 4d388c376..3f45b618a 100644 --- a/variants/nrf52840/canaryone/variant.h +++ b/variants/nrf52840/canaryone/variant.h @@ -52,8 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 -#define LED_CONN PIN_LED3 - #define LED_STATE_ON 0 // State when LED is lit /* diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h index 407bc1541..6927f1295 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h @@ -36,7 +36,6 @@ extern "C" { #define PIN_LED3 LED_RED #define PIN_LED PIN_LED1 -#define LED_PWR (PINS_COUNT) #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 282953cd9..0e37eeeff 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -48,8 +48,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -62,8 +60,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h index 96fa35869..2802e4c1d 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/variant.h @@ -137,8 +137,6 @@ No longer populated on PCB #define PIN_SPI1_MOSI PIN_EINK_MOSI #define PIN_SPI1_SCK PIN_EINK_SCLK -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index b3aa942ff..fe825089e 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -192,8 +192,6 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/heltec_mesh_pocket/variant.h b/variants/nrf52840/heltec_mesh_pocket/variant.h index c80fefd58..a86faf575 100644 --- a/variants/nrf52840/heltec_mesh_pocket/variant.h +++ b/variants/nrf52840/heltec_mesh_pocket/variant.h @@ -26,7 +26,6 @@ extern "C" { #define LED_RED PIN_LED1 #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED1 -#define LED_CONN LED_BLUE #define LED_STATE_ON 0 // State when LED is lit /* @@ -99,8 +98,6 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 5) #define PIN_SPI_SCK (0 + 4) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h index 298475401..8d4db4bea 100644 --- a/variants/nrf52840/heltec_mesh_solar/variant.h +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -132,8 +132,6 @@ No longer populated on PCB #define PIN_SPI_MOSI (0 + 22) #define PIN_SPI_SCK (0 + 19) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index d2cffd169..4a99def7d 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -49,8 +49,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -65,8 +63,6 @@ extern "C" { #define INPUTDRIVER_ENCODER_BTN 28 #define UPDOWN_LONG_PRESS_REPEAT_INTERVAL 150 -#define CANNED_MESSAGE_MODULE_ENABLE 1 - /* * Buzzer - PWM */ diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index e5896091b..9560d076b 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -52,8 +52,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) // Connected to WWAN host LED (if present) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -66,8 +64,6 @@ extern "C" { // #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index 8c3451835..6e7cbdc45 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -50,8 +50,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_CONN PIN_GREEN - #define LED_STATE_ON 0 // State when LED is lit /* @@ -152,8 +150,6 @@ External serial flash W25Q16JV_IQ #define PIN_SPI_MOSI (0 + 11) #define PIN_SPI_SCK (0 + 12) -// #define PIN_PWR_EN (0 + 6) - // To debug via the segger JLINK console rather than the CDC-ACM serial device // #define USE_SEGGER diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index 830126812..acad61a51 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (32 + 4) // P1.04 Controls Green LED #define PIN_LED2 (28) // P0.28 Controls Blue LED -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index 2fa0bd1e7..b6b8f0d6d 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -48,8 +48,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -62,8 +60,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index 4cc565d98..f98f7c400 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index b0215ad28..fd1e7cc4b 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index 574a0e68f..8029baf81 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index 7f1a5d112..9e57f9503 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -29,8 +29,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index 2e8ae129c..0fcffe5b6 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 84793bfb3..50c6ab573 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h index c31443371..354772e66 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.h +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index a6eccc3c8..2cd7279f9 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -47,8 +47,6 @@ extern "C" { #define PIN_LED1 (35) #define PIN_LED2 (36) -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 @@ -61,8 +59,6 @@ extern "C" { #define PIN_BUTTON1 9 // Pin for button on E-ink button module or IO expansion such as the RAK14014 or RAK14015 TFT modules #define BUTTON_NEED_PULLUP #define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 /* * Analog pins @@ -282,7 +278,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define RAK14014 // Tell it we have a RAK14014 #define USER_SETUP_LOADED 1 -#define DISABLE_ALL_LIBRARY_WARNINGS 1 #define ST7789_DRIVER 1 #define TFT_WIDTH 240 #define TFT_HEIGHT 320 @@ -310,7 +305,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define USE_POWERSAVE #define SLEEP_TIME 120 -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define USE_VIRTUAL_KEYBOARD 1 /*---------------------------------------------------------------------------- * Arduino objects - C++ only diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index 7473fd073..cbc8181a9 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -23,8 +23,6 @@ #define PIN_LED1 (12) // LED P1.15 #define PIN_LED2 (11) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // #define LED_PIN PIN_LED2 diff --git a/variants/nrf52840/seeed_wio_tracker_L1/variant.h b/variants/nrf52840/seeed_wio_tracker_L1/variant.h index 70b35336b..2d0ff2020 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1/variant.h @@ -23,8 +23,6 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // #define LED_PIN PIN_LED2 @@ -157,8 +155,6 @@ static const uint8_t SCL = PIN_WIRE_SCL; // joystick // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -#define CANNED_MESSAGE_MODULE_ENABLE 1 - #define CANNED_MESSAGE_ADD_CONFIRMATION 1 // trackball diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index 87aa5bb80..3dd1ca21b 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -23,8 +23,6 @@ #define PIN_LED1 (11) // LED P1.15 #define PIN_LED2 (12) // -#define LED_CONN PIN_LED2 - #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 // #define LED_PIN PIN_LED2 @@ -176,7 +174,6 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define TB_PRESS 29 #define TB_DIRECTION FALLING -#define CANNED_MESSAGE_MODULE_ENABLE 1 #define CANNED_MESSAGE_ADD_CONFIRMATION 1 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index e4305a57a..a9ccb2aae 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -69,7 +69,6 @@ static const uint8_t A5 = PIN_A5; #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define LED_PWR LED_RED #define USER_LED LED_BLUE /* diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index de93b10f4..7dc1a3ef5 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -52,7 +52,6 @@ extern "C" { #define BLE_LED LED_BLUE #define BLE_LED_INVERTED 1 -#define LED_CONN LED_GREEN #define LED_STATE_ON 0 // State when LED is lit // Buttons diff --git a/variants/nrf52840/t-echo-plus/variant.h b/variants/nrf52840/t-echo-plus/variant.h index 7e5235902..4038ce6ef 100644 --- a/variants/nrf52840/t-echo-plus/variant.h +++ b/variants/nrf52840/t-echo-plus/variant.h @@ -25,8 +25,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_CONN LED_GREEN - #define LED_STATE_ON 0 // Buttons / touch diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index 7714f5ce3..f6f650315 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -52,8 +52,6 @@ extern "C" { #define LED_BLUE PIN_LED1 #define LED_GREEN PIN_LED2 -#define LED_CONN PIN_GREEN - #define LED_STATE_ON 0 // State when LED is lit /* @@ -163,7 +161,6 @@ External serial flash WP25R1635FZUIL0 // Controls power for all peripherals (eink + GPS + LoRa + Sensor) #define PIN_POWER_EN (0 + 12) -// #define PIN_POWER_EN1 (0 + 13) #define PIN_SPI1_MISO \ (32 + 7) // FIXME not really needed, but for now the SPI code requires something to be defined, pick an used GPIO diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index 36c77d209..4d2b9ca3a 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -10,7 +10,6 @@ #define I2C_SDA1 2 #define I2C_SCL1 3 -#define LED_CONN PIN_LED2 #define LED_PIN PIN_LED #define ledOff(pin) pinMode(pin, INPUT) From dbded86dcb6647b06e6a19a2a9656472f1488989 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Jan 2026 12:51:48 -0600 Subject: [PATCH 057/223] More variant.h cleanup. LED_NOTIFICATION, remove dead code, etc (#9477) --- src/Power.cpp | 3 +++ src/configuration.h | 6 +++--- src/main.cpp | 6 +++--- src/mesh/NodeDB.cpp | 21 +++---------------- .../9m2ibr_aprs_lora_tracker/platformio.ini | 3 +++ .../diy/9m2ibr_aprs_lora_tracker/variant.cpp | 8 +++++++ .../esp32s3/hackaday-communicator/variant.h | 3 ++- variants/esp32s3/rak3312/variant.h | 2 +- variants/esp32s3/rak_wismesh_tap_v2/variant.h | 2 +- .../nrf52840/ELECROW-ThinkNode-M1/variant.cpp | 7 ------- .../nrf52840/ELECROW-ThinkNode-M1/variant.h | 7 +------ .../nrf52840/ELECROW-ThinkNode-M3/variant.h | 2 +- .../nrf52840/ELECROW-ThinkNode-M4/variant.cpp | 3 --- .../nrf52840/ELECROW-ThinkNode-M4/variant.h | 2 +- .../nrf52840/ELECROW-ThinkNode-M6/variant.h | 2 +- .../MakePython_nRF52840_eink/variant.cpp | 3 --- .../MakePython_nRF52840_eink/variant.h | 4 +--- .../MakePython_nRF52840_oled/variant.cpp | 3 --- .../MakePython_nRF52840_oled/variant.h | 4 +--- variants/nrf52840/feather_diy/variant.h | 2 +- .../gat562_mesh_trial_tracker/variant.cpp | 3 --- .../gat562_mesh_trial_tracker/variant.h | 4 ++-- variants/nrf52840/meshtiny/variant.cpp | 6 +++--- variants/nrf52840/meshtiny/variant.h | 3 +-- variants/nrf52840/monteops_hw1/variant.cpp | 3 --- variants/nrf52840/monteops_hw1/variant.h | 4 ++-- variants/nrf52840/muzi_base/variant.cpp | 4 ++-- variants/nrf52840/muzi_base/variant.h | 4 ++-- variants/nrf52840/nano-g2-ultra/variant.h | 9 -------- variants/nrf52840/nrf52.ini | 2 +- variants/nrf52840/r1-neo/variant.cpp | 3 --- variants/nrf52840/r1-neo/variant.h | 4 ++-- variants/nrf52840/rak2560/variant.cpp | 3 --- variants/nrf52840/rak2560/variant.h | 4 ++-- variants/nrf52840/rak3401_1watt/variant.cpp | 3 --- variants/nrf52840/rak3401_1watt/variant.h | 4 ++-- variants/nrf52840/rak4631/variant.cpp | 3 --- variants/nrf52840/rak4631/variant.h | 4 ++-- variants/nrf52840/rak4631_epaper/variant.cpp | 3 --- variants/nrf52840/rak4631_epaper/variant.h | 4 ++-- .../rak4631_epaper_onrxtx/variant.cpp | 3 --- .../nrf52840/rak4631_epaper_onrxtx/variant.h | 4 ++-- variants/nrf52840/rak4631_eth_gw/variant.cpp | 3 --- variants/nrf52840/rak4631_eth_gw/variant.h | 4 ++-- .../rak4631_nomadstar_meteor_pro/variant.cpp | 3 --- .../rak4631_nomadstar_meteor_pro/variant.h | 4 ++-- variants/nrf52840/rak_wismeshtag/variant.cpp | 3 --- variants/nrf52840/rak_wismeshtag/variant.h | 4 ++-- variants/nrf52840/rak_wismeshtap/variant.cpp | 3 --- variants/nrf52840/rak_wismeshtap/variant.h | 4 ++-- variants/nrf52840/seeed_solar_node/variant.h | 1 - .../nrf52840/seeed_wio_tracker_L1/variant.h | 3 +-- .../seeed_wio_tracker_L1_eink/variant.h | 3 +-- .../seeed_xiao_nrf52840_kit/variant.h | 2 -- .../nrf52840/wio-tracker-wm1110/variant.cpp | 3 --- .../nrf52840/wio-tracker-wm1110/variant.h | 5 +---- variants/rp2040/rak11310/pins_arduino.h | 2 +- variants/stm32/milesight_gs301/platformio.ini | 3 +++ variants/stm32/milesight_gs301/variant.cpp | 8 +++++++ 59 files changed, 82 insertions(+), 153 deletions(-) create mode 100644 variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp create mode 100644 variants/stm32/milesight_gs301/variant.cpp diff --git a/src/Power.cpp b/src/Power.cpp index b2a4ddaaf..b211d760e 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -816,6 +816,9 @@ void Power::shutdown() #endif #ifdef PIN_LED3 ledOff(PIN_LED3); +#endif +#ifdef LED_NOTIFICATION + ledOff(LED_NOTIFICATION); #endif doDeepSleep(DELAY_FOREVER, true, true); #elif defined(ARCH_PORTDUINO) diff --git a/src/configuration.h b/src/configuration.h index f7b438272..0a0525fe5 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -428,12 +428,12 @@ along with this program. If not, see . #define HAS_RGB_LED #endif -#ifndef LED_STATE_OFF -#define LED_STATE_OFF 0 -#endif #ifndef LED_STATE_ON #define LED_STATE_ON 1 #endif +#ifndef LED_STATE_OFF +#define LED_STATE_OFF (LED_STATE_ON ^ 1) +#endif // default mapping of pins #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) diff --git a/src/main.cpp b/src/main.cpp index 68eda2d0d..d9773dfb9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -354,9 +354,9 @@ void setup() digitalWrite(LED_POWER, LED_STATE_ON); #endif -#ifdef USER_LED - pinMode(USER_LED, OUTPUT); - digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +#ifdef LED_NOTIFICATION + pinMode(LED_NOTIFICATION, OUTPUT); + digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON); #endif #ifdef WIFI_LED diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 30d1633d5..0ba60f1e2 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -824,16 +824,10 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 500; moduleConfig.external_notification.nag_timeout = 2; #endif -#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \ - defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M6) - // Default to PIN_LED2 for external notification output (LED color depends on device variant) +#if defined(LED_NOTIFICATION) moduleConfig.external_notification.enabled = true; - moduleConfig.external_notification.output = PIN_LED2; -#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) - moduleConfig.external_notification.active = false; -#else - moduleConfig.external_notification.active = true; -#endif + moduleConfig.external_notification.output = LED_NOTIFICATION; + moduleConfig.external_notification.active = LED_STATE_ON; moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; @@ -857,15 +851,6 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.output_ms = 100; moduleConfig.external_notification.active = true; #endif -#ifdef ELECROW_ThinkNode_M1 - // Default to Elecrow USER_LED (blue) - moduleConfig.external_notification.enabled = true; - moduleConfig.external_notification.output = USER_LED; - moduleConfig.external_notification.active = true; - moduleConfig.external_notification.alert_message = true; - moduleConfig.external_notification.output_ms = 1000; - moduleConfig.external_notification.nag_timeout = 60; -#endif #ifdef T_LORA_PAGER moduleConfig.canned_message.updown1_enabled = true; moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A; diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini index 809599212..2ddc5a2db 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini @@ -10,3 +10,6 @@ build_flags = -D EBYTE_E22 -D EBYTE_E22_900M30S ; Assume Tx power curve is identical to 900M30S as there is no documentation -I variants/esp32/diy/9m2ibr_aprs_lora_tracker +build_src_filter = + ${esp32_base.build_src_filter} + +<../variants/esp32/diy/9m2ibr_aprs_lora_tracker> \ No newline at end of file diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp new file mode 100644 index 000000000..ef90d5a54 --- /dev/null +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.cpp @@ -0,0 +1,8 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(USER_LED, OUTPUT); + digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +} \ No newline at end of file diff --git a/variants/esp32s3/hackaday-communicator/variant.h b/variants/esp32s3/hackaday-communicator/variant.h index 7e048d72c..cca408622 100644 --- a/variants/esp32s3/hackaday-communicator/variant.h +++ b/variants/esp32s3/hackaday-communicator/variant.h @@ -46,4 +46,5 @@ #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -// #define LED_PIN 1 \ No newline at end of file +#define LED_NOTIFICATION 1 +#define LED_STATE_ON 0 diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index 4d860e476..6431f1fd0 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -22,7 +22,7 @@ #define LED_BLUE 45 #define PIN_LED1 LED_GREEN -#define PIN_LED2 LED_BLUE +#define LED_NOTIFICATION LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index f94b7ca4e..7d263165c 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -30,7 +30,7 @@ #define LED_BLUE 45 #define PIN_LED1 LED_GREEN -#define PIN_LED2 LED_BLUE +#define LED_NOTIFICATION LED_BLUE #define LED_PIN LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp index 1560cde73..04f86e2d4 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.cpp @@ -32,15 +32,8 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - - pinMode(PIN_LED3, OUTPUT); - ledOff(PIN_LED3); } void variant_shutdown() diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h index a8c7b42b5..e00e56785 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/variant.h @@ -41,20 +41,15 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -#define PIN_LED2 -1 -#define PIN_LED3 -1 - // LED #define PIN_LED1 (32 + 6) // red #define LED_POWER (32 + 4) -#define USER_LED (0 + 13) // green +#define LED_NOTIFICATION (0 + 13) // green // USB_CHECK #define EXT_PWR_DETECT (32 + 3) #define ADC_V (0 + 8) -#define LED_RED PIN_LED3 #define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 #define LED_STATE_ON 0 // State when LED is lit // LED灯亮时的状态 #define PIN_BUZZER (0 + 6) /* diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index 955a16a88..197b72cc9 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -54,7 +54,7 @@ extern "C" { #define LED_POWER LED_RED #define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED #define LED_GREEN 35 -#define PIN_LED2 LED_GREEN +#define LED_NOTIFICATION LED_GREEN #define LED_BLUE 37 #define LED_PAIRING LED_BLUE // Signals the Status LED Module to handle this LED diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp index 5c4b6215b..999f326db 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - pinMode(LED_PAIRING, OUTPUT); ledOff(LED_PAIRING); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h index 0d905c2c1..2cfe948e3 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M4/variant.h @@ -41,7 +41,7 @@ extern "C" { // LEDs #define LED_BLUE -1 -#define PIN_LED2 (32 + 9) +#define LED_NOTIFICATION (32 + 9) #define LED_PAIRING (13) #define Battery_LED_1 (15) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index 2461d73d2..387f4122b 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -43,7 +43,7 @@ extern "C" { #define LED_BLUE -1 #define LED_CHARGE (12) #define LED_PAIRING (7) -#define PIN_LED2 LED_PAIRING +#define LED_NOTIFICATION LED_PAIRING #define LED_STATE_ON HIGH #define LED_STATE_OFF LOW diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp b/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp index 8c6bf039c..04cda84ac 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.cpp @@ -32,7 +32,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/MakePython_nRF52840_eink/variant.h b/variants/nrf52840/MakePython_nRF52840_eink/variant.h index 48256e65f..64cca4916 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_eink/variant.h @@ -27,12 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 10) // LED P1.15 -#define PIN_LED2 (-1) // #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 -#define LED_STATE_ON 0 // State when LED is litted +#define LED_STATE_ON 0 // State when LED is lit /* * Buttons diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp b/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp index 8c6bf039c..04cda84ac 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.cpp @@ -32,7 +32,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/MakePython_nRF52840_oled/variant.h b/variants/nrf52840/MakePython_nRF52840_oled/variant.h index 025324037..2f035e7da 100644 --- a/variants/nrf52840/MakePython_nRF52840_oled/variant.h +++ b/variants/nrf52840/MakePython_nRF52840_oled/variant.h @@ -27,12 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 10) // LED P1.15 -#define PIN_LED2 (-1) // #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 -#define LED_STATE_ON 0 // State when LED is litted +#define LED_STATE_ON 0 // State when LED is lit /* * Buttons diff --git a/variants/nrf52840/feather_diy/variant.h b/variants/nrf52840/feather_diy/variant.h index a9782a8ee..a816e7867 100644 --- a/variants/nrf52840/feather_diy/variant.h +++ b/variants/nrf52840/feather_diy/variant.h @@ -52,7 +52,7 @@ extern "C" { #define LED_GREEN PIN_LED2 // Actually red #define LED_BLUE PIN_LED1 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit #define BUTTON_PIN (32 + 2) // P1.02 7 diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp b/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 0e37eeeff..2795a6141 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -46,10 +46,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/meshtiny/variant.cpp b/variants/nrf52840/meshtiny/variant.cpp index 2e8b00e4b..aae3da9f2 100644 --- a/variants/nrf52840/meshtiny/variant.cpp +++ b/variants/nrf52840/meshtiny/variant.cpp @@ -32,12 +32,12 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 + // LED1 & LED_BLUE pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); + pinMode(LED_BLUE, OUTPUT); + ledOff(LED_BLUE); // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); diff --git a/variants/nrf52840/meshtiny/variant.h b/variants/nrf52840/meshtiny/variant.h index 4a99def7d..4289fef4c 100644 --- a/variants/nrf52840/meshtiny/variant.h +++ b/variants/nrf52840/meshtiny/variant.h @@ -47,10 +47,9 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/monteops_hw1/variant.cpp b/variants/nrf52840/monteops_hw1/variant.cpp index 75cca1dc3..81ae9f482 100644 --- a/variants/nrf52840/monteops_hw1/variant.cpp +++ b/variants/nrf52840/monteops_hw1/variant.cpp @@ -35,7 +35,4 @@ void initVariant() // LED1 & LED2 pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); } diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index 9560d076b..b5411bf04 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -50,10 +50,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) // Connected to WWAN host LED (if present) +#define LED_BLUE (36) // Connected to WWAN host LED (if present) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/muzi_base/variant.cpp b/variants/nrf52840/muzi_base/variant.cpp index da01de974..e6178a968 100644 --- a/variants/nrf52840/muzi_base/variant.cpp +++ b/variants/nrf52840/muzi_base/variant.cpp @@ -63,8 +63,8 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); digitalWrite(PIN_LED1, HIGH); - pinMode(PIN_LED2, OUTPUT); - digitalWrite(PIN_LED2, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); // Initialize LoRa pins pinMode(SX126X_RESET, OUTPUT); diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index 1ac8d9c27..b82382314 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -43,9 +43,9 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 3) // P1.03, Green -#define PIN_LED2 (32 + 4) // P1.04, Blue +#define LED_BLUE (32 + 4) // P1.04, Blue -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 0 // State when LED is lit // Buttons diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index 6e7cbdc45..5fa6ed9dd 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -41,15 +41,6 @@ extern "C" { #define NUM_ANALOG_INPUTS (1) #define NUM_ANALOG_OUTPUTS (0) -// LEDs -#define PIN_LED1 (-1) -#define PIN_LED2 (-1) -#define PIN_LED3 (-1) - -#define LED_RED PIN_LED3 -#define LED_BLUE PIN_LED1 -#define LED_GREEN PIN_LED2 - #define LED_STATE_ON 0 // State when LED is lit /* diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini index a07fefb2f..727d2c741 100644 --- a/variants/nrf52840/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -7,7 +7,7 @@ extends = arduino_base platform_packages = ; our custom Git version until they merge our PR # TODO renovate - platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39 + platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#74096746e5f167a2ff22e483d8e79bb1aef00591 ; Don't renovate toolchain-gccarmnoneeabi platformio/toolchain-gccarmnoneeabi@~1.90301.0 diff --git a/variants/nrf52840/r1-neo/variant.cpp b/variants/nrf52840/r1-neo/variant.cpp index d87b88c85..c36e88602 100644 --- a/variants/nrf52840/r1-neo/variant.cpp +++ b/variants/nrf52840/r1-neo/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail // pinMode(PIN_3V3_EN, OUTPUT); // digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index acad61a51..1d2bc9bab 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (32 + 4) // P1.04 Controls Green LED -#define PIN_LED2 (28) // P0.28 Controls Blue LED +#define LED_BLUE (28) // P0.28 Controls Blue LED #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak2560/variant.cpp b/variants/nrf52840/rak2560/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak2560/variant.cpp +++ b/variants/nrf52840/rak2560/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index b6b8f0d6d..0038f295d 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -46,10 +46,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak3401_1watt/variant.cpp b/variants/nrf52840/rak3401_1watt/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak3401_1watt/variant.cpp +++ b/variants/nrf52840/rak3401_1watt/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index f98f7c400..8df0faccb 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631/variant.cpp b/variants/nrf52840/rak4631/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631/variant.cpp +++ b/variants/nrf52840/rak4631/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index fd1e7cc4b..edf4205f7 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631_epaper/variant.cpp b/variants/nrf52840/rak4631_epaper/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_epaper/variant.cpp +++ b/variants/nrf52840/rak4631_epaper/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index 8029baf81..19a2615f9 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp b/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index 9e57f9503..f1a1c58eb 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -27,10 +27,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631_eth_gw/variant.cpp b/variants/nrf52840/rak4631_eth_gw/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.cpp +++ b/variants/nrf52840/rak4631_eth_gw/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index 0fcffe5b6..24e0c0cd3 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 50c6ab573..655cea6e4 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak_wismeshtag/variant.cpp b/variants/nrf52840/rak_wismeshtag/variant.cpp index e84b60b3b..a035fbaf0 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.cpp +++ b/variants/nrf52840/rak_wismeshtag/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak_wismeshtag/variant.h b/variants/nrf52840/rak_wismeshtag/variant.h index 354772e66..5b20e4d93 100644 --- a/variants/nrf52840/rak_wismeshtag/variant.h +++ b/variants/nrf52840/rak_wismeshtag/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/rak_wismeshtap/variant.cpp b/variants/nrf52840/rak_wismeshtap/variant.cpp index 36572b074..73d2fac04 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.cpp +++ b/variants/nrf52840/rak_wismeshtap/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index 2cd7279f9..a31bc7b15 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -45,10 +45,10 @@ extern "C" { // LEDs #define PIN_LED1 (35) -#define PIN_LED2 (36) +#define LED_BLUE (36) #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_NOTIFICATION LED_BLUE #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/nrf52840/seeed_solar_node/variant.h b/variants/nrf52840/seeed_solar_node/variant.h index cbc8181a9..69736a9e0 100644 --- a/variants/nrf52840/seeed_solar_node/variant.h +++ b/variants/nrf52840/seeed_solar_node/variant.h @@ -25,7 +25,6 @@ #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 #define LED_STATE_ON 1 // State when LED is litted // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration diff --git a/variants/nrf52840/seeed_wio_tracker_L1/variant.h b/variants/nrf52840/seeed_wio_tracker_L1/variant.h index 2d0ff2020..a1ec2508a 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1/variant.h @@ -25,8 +25,7 @@ #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index 3dd1ca21b..495c4ace8 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -25,8 +25,7 @@ #define LED_GREEN PIN_LED1 #define LED_BLUE PIN_LED2 -// #define LED_PIN PIN_LED2 -#define LED_STATE_ON 1 // State when LED is litted +#define LED_STATE_ON 1 // State when LED is lit // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index a9ccb2aae..0d599d313 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -69,8 +69,6 @@ static const uint8_t A5 = PIN_A5; #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define USER_LED LED_BLUE - /* * Buttons */ diff --git a/variants/nrf52840/wio-tracker-wm1110/variant.cpp b/variants/nrf52840/wio-tracker-wm1110/variant.cpp index 5a3587982..0d0856773 100644 --- a/variants/nrf52840/wio-tracker-wm1110/variant.cpp +++ b/variants/nrf52840/wio-tracker-wm1110/variant.cpp @@ -36,9 +36,6 @@ void initVariant() pinMode(PIN_LED1, OUTPUT); ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); - // 3V3 Power Rail pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/wio-tracker-wm1110/variant.h b/variants/nrf52840/wio-tracker-wm1110/variant.h index 145750da5..647bd47d8 100644 --- a/variants/nrf52840/wio-tracker-wm1110/variant.h +++ b/variants/nrf52840/wio-tracker-wm1110/variant.h @@ -51,11 +51,8 @@ extern "C" { #define PIN_WIRE_SDA (0 + 5) // P0.05 #define PIN_WIRE_SCL (0 + 4) // P0.04 -#define PIN_LED1 (0 + 6) // P0.06 -#define PIN_LED2 (PINS_COUNT) // P0.14 - +#define PIN_LED1 (0 + 6) // P0.06 #define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 #define LED_STATE_ON 0 diff --git a/variants/rp2040/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h index 95ee308bc..56214a947 100644 --- a/variants/rp2040/rak11310/pins_arduino.h +++ b/variants/rp2040/rak11310/pins_arduino.h @@ -25,7 +25,7 @@ static const uint8_t A3 = PIN_A3; // LEDs #define PIN_LED (23u) #define PIN_LED1 PIN_LED -#define PIN_LED2 (24u) +#define LED_NOTIFICATION (24u) #define ADC_RESOLUTION 12 diff --git a/variants/stm32/milesight_gs301/platformio.ini b/variants/stm32/milesight_gs301/platformio.ini index a1d3fb34e..73b9cf7ea 100644 --- a/variants/stm32/milesight_gs301/platformio.ini +++ b/variants/stm32/milesight_gs301/platformio.ini @@ -15,6 +15,9 @@ build_flags = -DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1 build_unflags = -DDEBUG_MUTE # We have space for debug output until sensor support is added +build_src_filter = + ${stm32_base.build_src_filter} + +<../variants/stm32/milesight_gs301> lib_deps = ${stm32_base.lib_deps} diff --git a/variants/stm32/milesight_gs301/variant.cpp b/variants/stm32/milesight_gs301/variant.cpp new file mode 100644 index 000000000..ef90d5a54 --- /dev/null +++ b/variants/stm32/milesight_gs301/variant.cpp @@ -0,0 +1,8 @@ +#include "variant.h" +#include "Arduino.h" + +void earlyInitVariant() +{ + pinMode(USER_LED, OUTPUT); + digitalWrite(USER_LED, HIGH ^ LED_STATE_ON); +} \ No newline at end of file From eeb7373043f748d67a37ed54a91628413b02c3b3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Jan 2026 13:23:49 -0600 Subject: [PATCH 058/223] Remove errant symbol --- src/mesh/RadioInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 2a9b4e8f3..bbd766329 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -27,7 +27,7 @@ #include "platform/portduino/USBHal.h" #endif -#ifdef ARCH_STM32WL> +#ifdef ARCH_STM32WL #include "STM32WLE5JCInterface.h" #endif From 5dd06edd007b870744e4da930d6c953be0e5a538 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 29 Jan 2026 13:24:10 -0600 Subject: [PATCH 059/223] Add ledOff if not defined --- src/configuration.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/configuration.h b/src/configuration.h index 0a0525fe5..45054e421 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -435,6 +435,10 @@ along with this program. If not, see . #define LED_STATE_OFF (LED_STATE_ON ^ 1) #endif +#ifndef ledOff +#define ledOff(pin) pinMode(pin, INPUT) +#endif + // default mapping of pins #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) #define ALT_BUTTON_PIN PIN_BUTTON2 From 28b4f37a93af63f8895adc56c295b6885fc5211b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Jan 2026 20:14:50 -0600 Subject: [PATCH 060/223] Revert "Update libch341-spi-userspace digest to af9bc27 (#9472)" (#9483) This reverts commit b18742c2112309199150f39cb5a85a718290690e. --- variants/native/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index f99ee9a6f..b86420291 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -29,7 +29,7 @@ lib_deps = # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 # 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/af9bc27c9c30fa90772279925b7c5913dff789b4.zip + 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 adafruit/Adafruit seesaw Library@1.7.9 # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main From e08c0507206b3efc8aad90e17ff6b6b83560b8f8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 Jan 2026 20:15:08 -0600 Subject: [PATCH 061/223] Add custom ringtone definition for RAK4631 and enable buzzer pin (#9481) --- variants/nrf52840/rak4631/variant.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index b0215ad28..8b149a4fc 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -252,9 +252,13 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define RV3028_RTC (uint8_t)0b1010010 // RAK18001 Buzzer in Slot C -// #define PIN_BUZZER 21 // IO3 is PWM2 +#define PIN_BUZZER 21 // IO3 is PWM2 // NEW: set this via protobuf instead! +// RAK4631 custom ringtone +#undef USERPREFS_RINGTONE_RTTTL +#define USERPREFS_RINGTONE_RTTTL "Rak:d=32,o=5,b=200:b7,p,b7,4p,p" + // Battery // The battery sense is hooked to pin A0 (5) #define BATTERY_PIN PIN_A0 From 6f5a7672b483fe97b2e1341ea5ae2a9be11cd393 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:18:34 -0600 Subject: [PATCH 062/223] Update pschatzmann_arduino-audio-driver to v0.2.1 (#9398) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32s3/tlora-pager/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 63cb2a614..b5c9fd579 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -45,7 +45,7 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 # renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver - https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.0.zip + https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.1.zip # TODO renovate https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip # TODO renovate From 22617076f8f82f61c28ab97d1c4b2bda30413a23 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:19:12 -0600 Subject: [PATCH 063/223] Update meshtastic/device-ui digest to 63967a4 (#9475) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 25fc8829f..77e9cf214 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,7 +120,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/69739b84f87a91568d3c421498bc89977937a141.zip + https://github.com/meshtastic/device-ui/archive/63967a4a557d33d56fc5746f9128200dde2d88c5.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 68733a6c51289f80649269d5db6edaae894561ba Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 30 Jan 2026 06:32:08 -0600 Subject: [PATCH 064/223] Fix issue triage workflow by clarifying device log requirements and improving JSON response handling --- .github/workflows/models_issue_triage.yml | 41 ++++++++++++++--------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/.github/workflows/models_issue_triage.yml b/.github/workflows/models_issue_triage.yml index f61a15fe6..ef12885f8 100644 --- a/.github/workflows/models_issue_triage.yml +++ b/.github/workflows/models_issue_triage.yml @@ -100,7 +100,12 @@ jobs: prompt: | Analyze this GitHub issue for completeness and determine if it needs labels. - If this looks like a bug on the device/firmware (crash, reboot, lockup, radio issues, GPS issues, display issues, power/sleep issues), request device logs and explain how to get them: + IMPORTANT: Distinguish between: + - Device/firmware bugs (crashes, reboots, lockups, radio/GPS/display/power issues) - these need device logs + - Build/release/packaging issues (missing files, CI failures, download problems) - these do NOT need device logs + - Documentation or website issues - these do NOT need device logs + + If this is a device/firmware bug, request device logs and explain how to get them: Web Flasher logs: - Go to https://flasher.meshtastic.org @@ -113,20 +118,18 @@ jobs: Also request key context if missing: device model/variant, firmware version, region, steps to reproduce, expected vs actual. - Respond ONLY with JSON: - { - "complete": true|false, - "comment": "Your helpful comment requesting missing info, or empty string if complete", - "label": "needs-logs" | "needs-info" | "none" - } + Respond ONLY with valid JSON (no markdown, no code fences): + {"complete": true, "comment": "", "label": "none"} + OR + {"complete": false, "comment": "Your helpful comment", "label": "needs-logs"} - Use "needs-logs" if this is a device bug AND no logs are attached. + Use "needs-logs" ONLY if this is a device/firmware bug AND no logs are attached. Use "needs-info" if basic info like firmware version or steps to reproduce are missing. - Use "none" if the issue is complete or is a feature request. + Use "none" if the issue is complete, is a feature request, or is a build/CI/packaging issue. Title: ${{ github.event.issue.title }} Body: ${{ github.event.issue.body }} - system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels. + system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels. Only request device logs for actual device/firmware bugs, not for build/release/CI issues. model: openai/gpt-4o-mini - name: Process analysis result @@ -137,9 +140,12 @@ jobs: AI_RESPONSE: ${{ steps.analysis.outputs.response }} with: script: | - const raw = (process.env.AI_RESPONSE || '').trim(); + let raw = (process.env.AI_RESPONSE || '').trim(); - let complete = false; + // Strip markdown code fences if present + raw = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim(); + + let complete = true; let comment = ''; let label = 'none'; @@ -149,9 +155,10 @@ jobs: comment = (parsed.comment ?? '').toString().trim(); label = (parsed.label ?? 'none').toString().trim().toLowerCase(); } catch { - // If JSON parse fails, treat as incomplete with raw response as comment - complete = false; - comment = raw; + // If JSON parse fails, log warning and don't comment (avoid posting raw JSON) + console.log('Failed to parse AI response as JSON:', raw); + complete = true; + comment = ''; label = 'none'; } @@ -159,7 +166,9 @@ jobs: const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']); if (!allowedLabels.has(label)) label = 'none'; - core.setOutput('should_comment', (!complete && comment.length > 0) ? 'true' : 'false'); + // Only comment if we have a valid parsed comment (not raw JSON) + const shouldComment = !complete && comment.length > 0 && !comment.startsWith('{'); + core.setOutput('should_comment', shouldComment ? 'true' : 'false'); core.setOutput('comment_body', comment); core.setOutput('label', label); From ad4b1d9c2ba3232e4098eef0ea2bbcbd963216d4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 09:46:41 -0600 Subject: [PATCH 065/223] re-enable RTC support on THINKNODE M3 and M6 --- src/gps/RTC.cpp | 1 - variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M3/variant.h | 4 ++-- variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M6/variant.h | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index ad26b55a4..b13406e96 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -276,7 +276,6 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd settimeofday(tv, NULL); #endif - // nrf52 doesn't have a readable RTC (yet - software not written) #if HAS_RTC readFromRTC(); #endif diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini index 6751dd4ef..7a87c2a21 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini @@ -25,4 +25,4 @@ lib_deps = # renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM khoih-prog/nRF52_PWM@1.0.1 ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.4 + lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index acbcf6ae5..74440e3c3 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -115,8 +115,8 @@ extern "C" { #define SERIAL_PRINT_PORT 0 // PCF8563 RTC Module -// REVISIT https://github.com/meshtastic/firmware/pull/9084 -// #define PCF8563_RTC 0x51 +#define PCF8563_RTC 0x51 +#define HAS_RTC 1 #ifdef __cplusplus } diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini index a31615545..329111ce6 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini @@ -22,4 +22,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW lib_deps = ${nrf52840_base.lib_deps} ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - ; lewisxhe/SensorLib@0.3.4 + lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index 2461d73d2..780919a1f 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -120,8 +120,8 @@ static const uint8_t A0 = PIN_A0; #define PIN_SERIAL2_TX (24) // PCF8563 RTC Module -// REVISIT https://github.com/meshtastic/firmware/pull/9084 -// #define PCF8563_RTC 0x51 +#define PCF8563_RTC 0x51 +#define HAS_RTC 1 // SPI #define SPI_INTERFACES_COUNT 1 From 4cf01e7e53e3158f604d62b8de30c884cbc30b2b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 10:05:26 -0600 Subject: [PATCH 066/223] Adjust pin poweroff for Thinknode M6 --- variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index 4ce8ecdf0..bc0381a48 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -48,7 +48,7 @@ void variant_shutdown() // This sets the pin to OUTPUT and LOW for the pins *not* in the if block. for (int pin = 0; pin < 48; pin++) { if (pin == PIN_GPS_EN || pin == ADC_CTRL || pin == PIN_BUTTON1 || pin == PIN_SPI_MISO || pin == PIN_SPI_MOSI || - pin == PIN_SPI_SCK) { + pin == PIN_SPI_SCK || pin == SX126X_CS || pin == SX126X_RESET || pin == SX126X_BUSY || pin == SX126X_DIO1) { continue; } pinMode(pin, OUTPUT); From 200e79e80065d34a3a63abc8848b242ee559ab3b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 11:54:49 -0600 Subject: [PATCH 067/223] You get an RTC, and you get an RTC! (delete HAS_RTC as it wasn't actually doing much) (#9493) --- src/configuration.h | 3 --- src/gps/RTC.cpp | 3 --- src/platform/esp32/architecture.h | 3 --- src/platform/portduino/architecture.h | 3 --- variants/esp32/m5stack_coreink/variant.h | 1 - variants/esp32s3/t-watch-s3/variant.h | 1 - variants/esp32s3/tbeam-s3-core/variant.h | 1 - variants/esp32s3/tlora-pager/variant.h | 1 - variants/nrf52840/gat562_mesh_trial_tracker/variant.h | 2 -- variants/nrf52840/heltec_mesh_node_t114/variant.h | 1 - variants/nrf52840/monteops_hw1/variant.h | 2 -- variants/nrf52840/muzi_base/variant.h | 1 - variants/nrf52840/nano-g2-ultra/variant.h | 1 - variants/nrf52840/r1-neo/variant.h | 2 -- variants/nrf52840/rak2560/variant.h | 2 -- variants/nrf52840/rak3401_1watt/variant.h | 2 -- variants/nrf52840/rak4631/variant.h | 2 -- variants/nrf52840/rak4631_epaper/variant.h | 2 -- variants/nrf52840/rak4631_epaper_onrxtx/variant.h | 2 -- variants/nrf52840/rak4631_eth_gw/variant.h | 2 -- variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h | 2 -- variants/nrf52840/rak_wismeshtap/variant.h | 2 -- variants/nrf52840/t-echo/variant.h | 1 - 23 files changed, 42 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 45054e421..66fa4492d 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -390,9 +390,6 @@ along with this program. If not, see . #ifndef HAS_RADIO #define HAS_RADIO 0 #endif -#ifndef HAS_RTC -#define HAS_RTC 0 -#endif #ifndef HAS_CPU_SHUTDOWN #define HAS_CPU_SHUTDOWN 0 #endif diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index b13406e96..3bca6f6ec 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -276,10 +276,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd settimeofday(tv, NULL); #endif -#if HAS_RTC readFromRTC(); -#endif - return RTCSetResultSuccess; } else { return RTCSetResultNotSet; // RTC was already set with a higher quality time diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index f34f1fc65..7aee45f81 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -33,9 +33,6 @@ #ifndef HAS_RADIO #define HAS_RADIO 1 #endif -#ifndef HAS_RTC -#define HAS_RTC 1 -#endif #ifndef HAS_CPU_SHUTDOWN #define HAS_CPU_SHUTDOWN 1 #endif diff --git a/src/platform/portduino/architecture.h b/src/platform/portduino/architecture.h index 9ee8ad366..b1698a4eb 100644 --- a/src/platform/portduino/architecture.h +++ b/src/platform/portduino/architecture.h @@ -17,9 +17,6 @@ #ifndef HAS_RADIO #define HAS_RADIO 1 #endif -#ifndef HAS_RTC -#define HAS_RTC 1 -#endif #ifndef HAS_TELEMETRY #define HAS_TELEMETRY 1 #endif diff --git a/variants/esp32/m5stack_coreink/variant.h b/variants/esp32/m5stack_coreink/variant.h index b1708352b..9bf45f2ff 100644 --- a/variants/esp32/m5stack_coreink/variant.h +++ b/variants/esp32/m5stack_coreink/variant.h @@ -15,7 +15,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 // Wheel // Down 37 diff --git a/variants/esp32s3/t-watch-s3/variant.h b/variants/esp32s3/t-watch-s3/variant.h index 216dda589..df275c31d 100644 --- a/variants/esp32s3/t-watch-s3/variant.h +++ b/variants/esp32s3/t-watch-s3/variant.h @@ -45,7 +45,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 #define I2C_SDA 10 // For QMC6310 sensors and screens #define I2C_SCL 11 // For QMC6310 sensors and screens diff --git a/variants/esp32s3/tbeam-s3-core/variant.h b/variants/esp32s3/tbeam-s3-core/variant.h index 1f900fcae..9ce4aade9 100644 --- a/variants/esp32s3/tbeam-s3-core/variant.h +++ b/variants/esp32s3/tbeam-s3-core/variant.h @@ -55,7 +55,6 @@ // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 // Specify the PMU as Wire1. In the t-beam-s3 core, PCF8563 and PMU share the bus #define PMU_USE_WIRE1 diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h index e2d4029be..d97f864c3 100644 --- a/variants/esp32s3/tlora-pager/variant.h +++ b/variants/esp32s3/tlora-pager/variant.h @@ -40,7 +40,6 @@ // PCF85063 RTC Module #define PCF85063_RTC 0x51 -#define HAS_RTC 1 // Rotary #define ROTARY_A (40) diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h index 2795a6141..b5632a7fb 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/variant.h +++ b/variants/nrf52840/gat562_mesh_trial_tracker/variant.h @@ -259,8 +259,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -// #define HAS_RTC 1 - // #define HAS_ETHERNET 1 // #define RAK_4631 1 diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index fe825089e..e7385c4bb 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -223,7 +223,6 @@ No longer populated on PCB // VBAT=4.04V #define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_2_8 -#define HAS_RTC 0 #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/monteops_hw1/variant.h b/variants/nrf52840/monteops_hw1/variant.h index b5411bf04..a7fa7125b 100644 --- a/variants/nrf52840/monteops_hw1/variant.h +++ b/variants/nrf52840/monteops_hw1/variant.h @@ -208,8 +208,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (1.73F) -// #define HAS_RTC 1 - #define HAS_ETHERNET 1 #define PIN_ETHERNET_RESET 21 diff --git a/variants/nrf52840/muzi_base/variant.h b/variants/nrf52840/muzi_base/variant.h index b82382314..0cbd0e3ef 100644 --- a/variants/nrf52840/muzi_base/variant.h +++ b/variants/nrf52840/muzi_base/variant.h @@ -38,7 +38,6 @@ extern "C" { #define COMPASS_ORIENTATION meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270 #define HAS_ICM20948 // forces the i2c address to be seen as this sensor -#define HAS_RTC 1 #define RX8130CE_RTC 0x32 // LEDs diff --git a/variants/nrf52840/nano-g2-ultra/variant.h b/variants/nrf52840/nano-g2-ultra/variant.h index 5fa6ed9dd..631af72d8 100644 --- a/variants/nrf52840/nano-g2-ultra/variant.h +++ b/variants/nrf52840/nano-g2-ultra/variant.h @@ -129,7 +129,6 @@ External serial flash W25Q16JV_IQ // PCF8563 RTC Module #define PIN_RTC_INT (0 + 14) // Interrupt from the PCF8563 RTC #define PCF8563_RTC 0x51 -#define HAS_RTC 1 /* * SPI Interfaces diff --git a/variants/nrf52840/r1-neo/variant.h b/variants/nrf52840/r1-neo/variant.h index 1d2bc9bab..42d44d673 100644 --- a/variants/nrf52840/r1-neo/variant.h +++ b/variants/nrf52840/r1-neo/variant.h @@ -132,8 +132,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define ADC_MULTIPLIER 1.667 #define OCV_ARRAY 4120, 4020, 4000, 3940, 3870, 3820, 3750, 3630, 3550, 3450, 3100 -#define HAS_RTC 1 - #define RX8130CE_RTC 0x32 #ifdef __cplusplus diff --git a/variants/nrf52840/rak2560/variant.h b/variants/nrf52840/rak2560/variant.h index 0038f295d..acd1ae60e 100644 --- a/variants/nrf52840/rak2560/variant.h +++ b/variants/nrf52840/rak2560/variant.h @@ -244,8 +244,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #define HALF_UART_PIN PIN_SERIAL1_RX diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index 8df0faccb..80b09cf69 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -208,8 +208,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #ifdef __cplusplus diff --git a/variants/nrf52840/rak4631/variant.h b/variants/nrf52840/rak4631/variant.h index 37ae0f12a..6a6b32f27 100644 --- a/variants/nrf52840/rak4631/variant.h +++ b/variants/nrf52840/rak4631/variant.h @@ -280,8 +280,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG // VDD=3.3V AIN3=6/8*VDD=2.47V VBAT=1.66*AIN3=4.1V #define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_11_16 -#define HAS_RTC 1 - #define HAS_ETHERNET 1 #define RAK_4631 1 diff --git a/variants/nrf52840/rak4631_epaper/variant.h b/variants/nrf52840/rak4631_epaper/variant.h index 19a2615f9..82c26af7b 100644 --- a/variants/nrf52840/rak4631_epaper/variant.h +++ b/variants/nrf52840/rak4631_epaper/variant.h @@ -217,8 +217,6 @@ static const uint8_t SCK = PIN_SPI_SCK; #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define RAK_4631 1 #ifdef __cplusplus diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h index f1a1c58eb..2d34ab84c 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/variant.h +++ b/variants/nrf52840/rak4631_epaper_onrxtx/variant.h @@ -192,8 +192,6 @@ static const uint8_t SCK = PIN_SPI_SCK; // #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 // #define ADC_MULTIPLIER 1.73 -// #define HAS_RTC 1 - #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/rak4631_eth_gw/variant.h b/variants/nrf52840/rak4631_eth_gw/variant.h index 24e0c0cd3..eb1d558ea 100644 --- a/variants/nrf52840/rak4631_eth_gw/variant.h +++ b/variants/nrf52840/rak4631_eth_gw/variant.h @@ -249,8 +249,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 1 - #define HAS_ETHERNET 1 #define RAK_4631 1 diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h index 655cea6e4..aea497305 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/variant.h @@ -244,8 +244,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER 1.73 -#define HAS_RTC 0 - #define HAS_ETHERNET 0 #define RAK_4631 1 diff --git a/variants/nrf52840/rak_wismeshtap/variant.h b/variants/nrf52840/rak_wismeshtap/variant.h index a31bc7b15..358117cd5 100644 --- a/variants/nrf52840/rak_wismeshtap/variant.h +++ b/variants/nrf52840/rak_wismeshtap/variant.h @@ -266,8 +266,6 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define ADC_MULTIPLIER (1.73F) -#define HAS_RTC 1 - #define RAK_4631 1 #define AQ_SET_PIN 10 diff --git a/variants/nrf52840/t-echo/variant.h b/variants/nrf52840/t-echo/variant.h index f6f650315..f4644c6de 100644 --- a/variants/nrf52840/t-echo/variant.h +++ b/variants/nrf52840/t-echo/variant.h @@ -188,7 +188,6 @@ External serial flash WP25R1635FZUIL0 // PCF8563 RTC Module #define PIN_RTC_INT (0 + 16) // Interrupt from the PCF8563 RTC #define PCF8563_RTC 0x51 -#define HAS_RTC 1 /* * SPI Interfaces From caae6bc5970a8fe30c2802c58565c6cefea63566 Mon Sep 17 00:00:00 2001 From: scobert969 Date: Fri, 30 Jan 2026 10:31:26 -0800 Subject: [PATCH 068/223] Change canned message recipient's previous page to send page (#9227) * Change canned message recipient's previous page to send page * Set previousPage for new menu pages Set nextPage in back MenuPages to previousPage Removed back MenuAction --------- Co-authored-by: zeropt Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- .../InkHUD/Applets/System/Menu/MenuAction.h | 1 - .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 80 +++++++++++-------- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h index 74ad5c85f..7cfb33e9d 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h @@ -22,7 +22,6 @@ enum MenuAction { STORE_CANNEDMESSAGE_SELECTION, SEND_CANNEDMESSAGE, SHUTDOWN, - BACK, NEXT_TILE, TOGGLE_BACKLIGHT, TOGGLE_GPS, diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 93d2c6b83..b69e31e9a 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -325,10 +325,6 @@ void InkHUD::MenuApplet::execute(MenuItem item) } break; - case BACK: - showPage(item.nextPage); - return; - case NEXT_TILE: inkhud->nextTile(); // Unselect menu item after tile change @@ -868,6 +864,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) switch (page) { case ROOT: + previousPage = MenuPage::EXIT; // Optional: next applet if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1) items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown @@ -878,7 +875,6 @@ void InkHUD::MenuApplet::showPage(MenuPage page) items.push_back(MenuItem("Node Config", MenuPage::NODE_CONFIG)); items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); - previousPage = MenuPage::EXIT; break; case SEND: @@ -888,11 +884,12 @@ void InkHUD::MenuApplet::showPage(MenuPage page) case CANNEDMESSAGE_RECIPIENT: populateRecipientPage(); - previousPage = MenuPage::OPTIONS; + previousPage = MenuPage::SEND; break; case OPTIONS: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT)); + previousPage = MenuPage::ROOT; + items.push_back(MenuItem("Back", previousPage)); // Optional: backlight if (settings->optionalMenuItems.backlight) items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label @@ -916,31 +913,32 @@ void InkHUD::MenuApplet::showPage(MenuPage page) invertedColors = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); items.push_back(MenuItem("Invert Color", MenuAction::TOGGLE_INVERT_COLOR, MenuPage::OPTIONS, &invertedColors)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); - previousPage = MenuPage::ROOT; break; case APPLETS: - populateAppletPage(); // must be first - items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); - items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; + populateAppletPage(); // must be first + items.insert(items.begin(), MenuItem("Back", previousPage)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; case AUTOSHOW: - populateAutoshowPage(); // must be first - items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); - items.push_back(MenuItem("Exit", MenuPage::EXIT)); previousPage = MenuPage::OPTIONS; + populateAutoshowPage(); // must be first + items.insert(items.begin(), MenuItem("Back", previousPage)); + items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; case RECENTS: + previousPage = MenuPage::OPTIONS; populateRecentsPage(); // builds only the options - items.insert(items.begin(), MenuItem("Back", MenuAction::BACK, MenuPage::OPTIONS)); + items.insert(items.begin(), MenuItem("Back", previousPage)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); break; case NODE_CONFIG: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::ROOT)); + previousPage = MenuPage::ROOT; + items.push_back(MenuItem("Back", previousPage)); // Radio Config Section items.push_back(MenuItem::Header("Radio Config")); items.push_back(MenuItem("LoRa", MenuPage::NODE_CONFIG_LORA)); @@ -965,8 +963,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) break; case NODE_CONFIG_DEVICE: { - - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); const char *role = DisplayFormatters::getDeviceRole(config.device.role); nodeConfigLabels.emplace_back("Role: " + std::string(role)); @@ -981,7 +979,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_POSITION: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); #if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS const auto mode = config.position.gps_mode; if (mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) { @@ -996,7 +995,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_POWER: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); #if defined(ARCH_ESP32) items.push_back(MenuItem("Powersave", MenuAction::TOGGLE_POWER_SAVE, MenuPage::EXIT, &config.power.is_power_saving)); #endif @@ -1029,7 +1029,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_POWER_ADC_CAL: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_POWER)); + previousPage = MenuPage::NODE_CONFIG_POWER; + items.push_back(MenuItem("Back", previousPage)); // Instruction text (header-style, non-selectable) items.push_back(MenuItem::Header("Run on full charge Only")); @@ -1042,7 +1043,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_NETWORK: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); const char *wifiLabel = config.network.wifi_enabled ? "WiFi: On" : "WiFi: Off"; @@ -1099,7 +1101,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_DISPLAY: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::NODE_CONFIG_DISPLAY, &config.display.use_12h_clock)); @@ -1114,7 +1117,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_BLUETOOTH: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); const char *btLabel = config.bluetooth.enabled ? "Bluetooth: On" : "Bluetooth: Off"; items.push_back(MenuItem(btLabel, MenuAction::TOGGLE_BLUETOOTH, MenuPage::EXIT)); @@ -1127,8 +1131,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_LORA: { - - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); const char *region = myRegion ? myRegion->name : "Unset"; nodeConfigLabels.emplace_back("Region: " + std::string(region)); @@ -1150,7 +1154,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_CHANNELS: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { meshtastic_Channel &ch = channels.getByIndex(i); @@ -1181,7 +1186,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_CHANNEL_DETAIL: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNELS)); + previousPage = MenuPage::NODE_CONFIG_CHANNELS; + items.push_back(MenuItem("Back", previousPage)); meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); @@ -1226,7 +1232,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_CHANNEL_PRECISION: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); + previousPage = MenuPage::NODE_CONFIG_CHANNEL_DETAIL; + items.push_back(MenuItem("Back", previousPage)); meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) { items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); @@ -1247,7 +1254,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case NODE_CONFIG_DEVICE_ROLE: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE)); + previousPage = MenuPage::NODE_CONFIG_DEVICE; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("Client", MenuAction::SET_ROLE_CLIENT, MenuPage::EXIT)); items.push_back(MenuItem("Client Mute", MenuAction::SET_ROLE_CLIENT_MUTE, MenuPage::EXIT)); items.push_back(MenuItem("Router", MenuAction::SET_ROLE_ROUTER, MenuPage::EXIT)); @@ -1257,7 +1265,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } case TIMEZONE: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_DEVICE)); + previousPage = MenuPage::NODE_CONFIG_DEVICE; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("US/Hawaii", SET_TZ_US_HAWAII, MenuPage::NODE_CONFIG_DEVICE)); items.push_back(MenuItem("US/Alaska", SET_TZ_US_ALASKA, MenuPage::NODE_CONFIG_DEVICE)); items.push_back(MenuItem("US/Pacific", SET_TZ_US_PACIFIC, MenuPage::NODE_CONFIG_DEVICE)); @@ -1279,7 +1288,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) break; case REGION: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA)); + previousPage = MenuPage::NODE_CONFIG_LORA; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT)); items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT)); items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT)); @@ -1310,7 +1320,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) break; case NODE_CONFIG_PRESET: { - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG_LORA)); + previousPage = MenuPage::NODE_CONFIG_LORA; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT)); items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT)); items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT)); @@ -1323,7 +1334,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page) } // Administration Section case NODE_CONFIG_ADMIN_RESET: - items.push_back(MenuItem("Back", MenuAction::BACK, MenuPage::NODE_CONFIG)); + previousPage = MenuPage::NODE_CONFIG; + items.push_back(MenuItem("Back", previousPage)); items.push_back(MenuItem("Reset All", MenuAction::RESET_NODEDB_ALL, MenuPage::EXIT)); items.push_back(MenuItem("Keep Favorites Only", MenuAction::RESET_NODEDB_KEEP_FAVORITES, MenuPage::EXIT)); items.push_back(MenuItem("Exit", MenuPage::EXIT)); From 7bbfe99fbef6d2f9302462662518295fe151a9fd Mon Sep 17 00:00:00 2001 From: scobert969 Date: Fri, 30 Jan 2026 10:35:10 -0800 Subject: [PATCH 069/223] Add on-screen keyboard to InkHUD (#9445) * Added keyboard option to menu. Shows a keyboard layout but does not type. * Keyboard types into text box and wraps. * send FreeText messages from the send submenu - renamed `KEYBOARD` action to `FREE_TEXT` and moved its menu location to the send submenu - opening the FreeText applet from the menu keeps the menu open and disabled the timeout - the FreeText applet writes to inkhud->freetext - the sending a canned message checks inkhud->freetext and if it isn't empty, sends and clears the inkhud->freetext * Text scrolls along with input * handle free text message completion as an event implements `handleFreeText` and `OnFreeText()` for system applets to interface with the FreeText Applet The FreeText Applet generates an `OnFreeText` event when completing a message which is handled by the first system applet with the `handleFreeText` flag set to true. The Menu Applet now handles this event. * call `onFreeText` whenever the FreeText Applet exits allows the menu to consistently restart its auto-close timeout * Add text cursor * Change UI to remove the header and make text box longer Keyboard displays captial letters for legibility Keyboard types captial letters with long press * center FreeText keys and draw symbolic buttons Move input field and keyboard drawing to their own functions: - `drawInputField()` - `drawKeyboard()` Store the keys in a 1-dimensional array Implement a matching array, `keyWidths`, to set key widths relative to the font size * Add character limit and counter * Fix softlock when hitting character limit * Move text box as its own menu page * rework FreeTextApplet into KeyboardApplet - The Keyboard Applet renders an on-screen keyboard at the lower portion of the screen. - Calling `inkhud->openKeyboard()` sends all the user applets to the background and resizes the first system applet with `handleFreeText` set to True to fit above the on-screen keyboard - `inkhud->closeKeyboard()` reverses this layout change * Fix input box rendering and add character limit to menu free text * remove FREE_TEXT menu page and use the FREE_TEXT menu action solely * force update when changing the free text message * reorganize KeyboardApplet - add comments after each row of `key[]` and `keyWidths[]` to preserve formatting - The selected key is now set using the key index directly - rowWidths are pre-calculated in the KeyboardApplet constructor - removed `drawKeyboard()` and implemented `drawKeyLabel()` * implement `Renderer::clearTile()` to clear the region below a tile * add parameter to forceUpdate() for re-rendering the full screen setting the `all` parameter to true in `inkhud->forceUpdate()` now causes the full screen buffer to clear an re-render. This is helpful for when sending applets to the background and the UI needs a clean canvas. System Applets can now set the `alwaysRender` flag true which causes it to re-render on every screen update. This is set to true in the Battery Icon Applet. * clean up tile clearing loops * implement dirty rendering to let applets draw over their previous render - `Applet::requestUpdate()` now has an optional flag to keep the old canvas - If honored, the renderer calls `render(true)` which runs `onDirtyRender()` instead of `onRender()` for said applet - The renderer will not call a dirty render if the full screen is getting re-rendered * simplify arithmetic in clearTile for better understanding * combine Applet::onRender() and Applet::onDirtyRender() into Applet::onRender(bool full) - add new `full` parameter to onRender() in every applet. This parameter can be ignored by most applets. - `Applet::requestUpdate()` has an optional flag that requests a full render by default * implement tile and partial rendering in KeyboardApplet * add comment for drawKeyLabel() * improve clarity of byte operations in clearTile() * remove typo and commented code * fix inaccurate comments * add null check to openKeyboard() and closeKeyboard() --------- Co-authored-by: zeropt Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- src/graphics/niche/InkHUD/Applet.cpp | 13 +- src/graphics/niche/InkHUD/Applet.h | 14 +- .../InkHUD/Applets/Bases/Map/MapApplet.cpp | 2 +- .../InkHUD/Applets/Bases/Map/MapApplet.h | 2 +- .../Applets/Bases/NodeList/NodeListApplet.cpp | 2 +- .../Applets/Bases/NodeList/NodeListApplet.h | 2 +- .../BasicExample/BasicExampleApplet.cpp | 2 +- .../BasicExample/BasicExampleApplet.h | 2 +- .../NewMsgExample/NewMsgExampleApplet.cpp | 2 +- .../NewMsgExample/NewMsgExampleApplet.h | 2 +- .../System/AlignStick/AlignStickApplet.cpp | 10 +- .../System/AlignStick/AlignStickApplet.h | 2 +- .../System/BatteryIcon/BatteryIconApplet.cpp | 4 +- .../System/BatteryIcon/BatteryIconApplet.h | 2 +- .../System/Keyboard/KeyboardApplet.cpp | 257 ++++++++++++++++++ .../Applets/System/Keyboard/KeyboardApplet.h | 66 +++++ .../InkHUD/Applets/System/Logo/LogoApplet.cpp | 10 +- .../InkHUD/Applets/System/Logo/LogoApplet.h | 2 +- .../InkHUD/Applets/System/Menu/MenuAction.h | 1 + .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 253 ++++++++++++----- .../InkHUD/Applets/System/Menu/MenuApplet.h | 12 +- .../Notification/NotificationApplet.cpp | 11 +- .../System/Notification/NotificationApplet.h | 2 +- .../Applets/System/Pairing/PairingApplet.cpp | 4 +- .../Applets/System/Pairing/PairingApplet.h | 2 +- .../System/Placeholder/PlaceholderApplet.cpp | 2 +- .../System/Placeholder/PlaceholderApplet.h | 2 +- .../InkHUD/Applets/System/Tips/TipsApplet.cpp | 9 +- .../InkHUD/Applets/System/Tips/TipsApplet.h | 2 +- .../User/AllMessage/AllMessageApplet.cpp | 2 +- .../User/AllMessage/AllMessageApplet.h | 2 +- .../niche/InkHUD/Applets/User/DM/DMApplet.cpp | 2 +- .../niche/InkHUD/Applets/User/DM/DMApplet.h | 2 +- .../User/Positions/PositionsApplet.cpp | 4 +- .../Applets/User/Positions/PositionsApplet.h | 2 +- .../ThreadedMessage/ThreadedMessageApplet.cpp | 2 +- .../ThreadedMessage/ThreadedMessageApplet.h | 2 +- src/graphics/niche/InkHUD/Events.cpp | 35 ++- src/graphics/niche/InkHUD/Events.h | 5 + src/graphics/niche/InkHUD/InkHUD.cpp | 36 ++- src/graphics/niche/InkHUD/InkHUD.h | 10 +- src/graphics/niche/InkHUD/Renderer.cpp | 113 +++++++- src/graphics/niche/InkHUD/Renderer.h | 6 +- src/graphics/niche/InkHUD/SystemApplet.h | 10 +- src/graphics/niche/InkHUD/Tile.cpp | 16 +- src/graphics/niche/InkHUD/Tile.h | 2 + src/graphics/niche/InkHUD/WindowManager.cpp | 56 +++- src/graphics/niche/InkHUD/WindowManager.h | 3 + src/graphics/niche/InkHUD/docs/README.md | 4 +- 49 files changed, 852 insertions(+), 158 deletions(-) create mode 100644 src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp create mode 100644 src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index 1e89ebe1b..ccdd76f97 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -55,7 +55,7 @@ InkHUD::Tile *InkHUD::Applet::getTile() } // Draw the applet -void InkHUD::Applet::render() +void InkHUD::Applet::render(bool full) { assert(assignedTile); // Ensure that we have a tile assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile @@ -65,10 +65,11 @@ void InkHUD::Applet::render() wantRender = false; // Flag set by requestUpdate wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored. wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted. + wantFullRender = true; // Default to a full render updateDimensions(); resetDrawingSpace(); - onRender(); // Derived applet's drawing takes place here + onRender(full); // Draw the applet // Handle "Tile Highlighting" // Some devices may use an auxiliary button to switch between tiles @@ -115,6 +116,11 @@ Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType() return wantUpdateType; } +bool InkHUD::Applet::wantsFullRender() +{ + return wantFullRender; +} + // Get size of the applet's drawing space from its tile // Performed immediately before derived applet's drawing code runs void InkHUD::Applet::updateDimensions() @@ -142,10 +148,11 @@ void InkHUD::Applet::resetDrawingSpace() // Once the renderer has given other applets a chance to process whatever event we just detected, // it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground) // We should requestUpdate even if our applet is currently background, because this might be changed by autoshow -void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type) +void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type, bool full) { wantRender = true; wantUpdateType = type; + wantFullRender = full; inkhud->requestUpdate(); } diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index b35ca5cc0..69d35a234 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -64,10 +64,11 @@ class Applet : public GFX // Rendering - void render(); // Draw the applet + void render(bool full); // Draw the applet bool wantsToRender(); // Check whether applet wants to render bool wantsToAutoshow(); // Check whether applet wants to become foreground Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer + bool wantsFullRender(); // Check whether applet wants to render over its previous render void updateDimensions(); // Get current size from tile void resetDrawingSpace(); // Makes sure every render starts with same parameters @@ -82,7 +83,7 @@ class Applet : public GFX // Event handlers - virtual void onRender() = 0; // All drawing happens here + virtual void onRender(bool full) = 0; // For drawing the applet virtual void onActivate() {} virtual void onDeactivate() {} virtual void onForeground() {} @@ -96,6 +97,9 @@ class Applet : public GFX virtual void onNavDown() {} virtual void onNavLeft() {} virtual void onNavRight() {} + virtual void onFreeText(char c) {} + virtual void onFreeTextDone() {} + virtual void onFreeTextCancel() {} virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification @@ -108,8 +112,9 @@ class Applet : public GFX protected: void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here - void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update - void requestAutoshow(); // Ask for applet to be moved to foreground + void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED, + bool full = true); // Ask WindowManager to schedule a display update + void requestAutoshow(); // Ask for applet to be moved to foreground uint16_t X(float f); // Map applet width, mapped from 0 to 1.0 uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0 @@ -164,6 +169,7 @@ class Applet : public GFX bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground? NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType = NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display + bool wantFullRender = true; // Render with a fresh canvas using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager. diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp index d383a11e4..4cf83966b 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.cpp @@ -4,7 +4,7 @@ using namespace NicheGraphics; -void InkHUD::MapApplet::onRender() +void InkHUD::MapApplet::onRender(bool full) { // Abort if no markers to render if (!enoughMarkers()) { diff --git a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h index f45a36071..11dfb39d9 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h @@ -27,7 +27,7 @@ namespace NicheGraphics::InkHUD class MapApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; protected: virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp index 5c9906fba..9794c3efb 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp @@ -103,7 +103,7 @@ uint8_t InkHUD::NodeListApplet::maxCards() } // Draw, using info which derived applet placed into NodeListApplet::cards for us -void InkHUD::NodeListApplet::onRender() +void InkHUD::NodeListApplet::onRender(bool full) { // ================================ diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h index c2340027b..8babdba03 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h @@ -46,7 +46,7 @@ class NodeListApplet : public Applet, public MeshModule public: NodeListApplet(const char *name); - void onRender() override; + void onRender(bool full) override; bool wantPacket(const meshtastic_MeshPacket *p) override; ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; diff --git a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp index c52719e55..71b6d9a7a 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.cpp @@ -6,7 +6,7 @@ using namespace NicheGraphics; // All drawing happens here // Our basic example doesn't do anything useful. It just passively prints some text. -void InkHUD::BasicExampleApplet::onRender() +void InkHUD::BasicExampleApplet::onRender(bool full) { printAt(0, 0, "Hello, World!"); diff --git a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h index aed63cdc8..a36f6e8d5 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h @@ -28,7 +28,7 @@ class BasicExampleApplet : public Applet // You must have an onRender() method // All drawing happens here - void onRender() override; + void onRender(bool full) override; }; } // namespace NicheGraphics::InkHUD diff --git a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp index 6b02f4c92..cf3fd7714 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.cpp @@ -35,7 +35,7 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh // We can trigger a render by calling requestUpdate() // Render might be called by some external source // We should always be ready to draw -void InkHUD::NewMsgExampleApplet::onRender() +void InkHUD::NewMsgExampleApplet::onRender(bool full) { printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0) diff --git a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h index 22670a0f0..599f08a7a 100644 --- a/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h +++ b/src/graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h @@ -34,7 +34,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {} // All drawing happens here - void onRender() override; + void onRender(bool full) override; // Your applet might also want to use some of these // Useful for setting up or tidying up diff --git a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp index 67ef87f41..3afa80149 100644 --- a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.cpp @@ -10,7 +10,7 @@ InkHUD::AlignStickApplet::AlignStickApplet() bringToForeground(); } -void InkHUD::AlignStickApplet::onRender() +void InkHUD::AlignStickApplet::onRender(bool full) { setFont(fontMedium); printAt(0, 0, "Align Joystick:"); @@ -152,19 +152,17 @@ void InkHUD::AlignStickApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } void InkHUD::AlignStickApplet::onButtonLongPress() { sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onExitLong() { sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavUp() @@ -172,7 +170,6 @@ void InkHUD::AlignStickApplet::onNavUp() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavDown() @@ -181,7 +178,6 @@ void InkHUD::AlignStickApplet::onNavDown() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavLeft() @@ -190,7 +186,6 @@ void InkHUD::AlignStickApplet::onNavLeft() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::AlignStickApplet::onNavRight() @@ -199,7 +194,6 @@ void InkHUD::AlignStickApplet::onNavRight() settings->joystick.aligned = true; sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } #endif \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h index 8dba33165..7c8d00155 100644 --- a/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/AlignStick/AlignStickApplet.h @@ -23,7 +23,7 @@ class AlignStickApplet : public SystemApplet public: AlignStickApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonLongPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp index 4f99d99ee..0cc6f50ed 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp @@ -6,6 +6,8 @@ using namespace NicheGraphics; InkHUD::BatteryIconApplet::BatteryIconApplet() { + alwaysRender = true; // render everytime the screen is updated + // Show at boot, if user has previously enabled the feature if (settings->optionalFeatures.batteryIcon) bringToForeground(); @@ -44,7 +46,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta return 0; // Tell Observable to continue informing other observers } -void InkHUD::BatteryIconApplet::onRender() +void InkHUD::BatteryIconApplet::onRender(bool full) { // Fill entire tile // - size of icon controlled by size of tile diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h index e5b4172be..ceaf88d7f 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.h @@ -23,7 +23,7 @@ class BatteryIconApplet : public SystemApplet public: BatteryIconApplet(); - void onRender() override; + void onRender(bool full) override; int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available private: diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp new file mode 100644 index 000000000..57581d56b --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.cpp @@ -0,0 +1,257 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD +#include "./KeyboardApplet.h" + +using namespace NicheGraphics; + +InkHUD::KeyboardApplet::KeyboardApplet() +{ + // Calculate row widths + for (uint8_t row = 0; row < KBD_ROWS; row++) { + rowWidths[row] = 0; + for (uint8_t col = 0; col < KBD_COLS; col++) + rowWidths[row] += keyWidths[row * KBD_COLS + col]; + } +} + +void InkHUD::KeyboardApplet::onRender(bool full) +{ + uint16_t em = fontSmall.lineHeight(); // 16 pt + uint16_t keyH = Y(1.0) / KBD_ROWS; + int16_t keyTopPadding = (keyH - fontSmall.lineHeight()) / 2; + + if (full) { // Draw full keyboard + for (uint8_t row = 0; row < KBD_ROWS; row++) { + + // Calculate the remaining space to be used as padding + int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + + // Draw keys + uint16_t xPos = 0; + for (uint8_t col = 0; col < KBD_COLS; col++) { + Color fgcolor = BLACK; + uint8_t index = row * KBD_COLS + col; + uint16_t keyX = ((xPos * em) >> 4) + ((col * keyXPadding) / (KBD_COLS - 1)); + uint16_t keyY = row * keyH; + uint16_t keyW = (keyWidths[index] * em) >> 4; + if (index == selectedKey) { + fgcolor = WHITE; + fillRect(keyX, keyY, keyW, keyH, BLACK); + } + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[index], fgcolor); + xPos += keyWidths[index]; + } + } + } else { // Only draw the difference + if (selectedKey != prevSelectedKey) { + // Draw previously selected key + uint8_t row = prevSelectedKey / KBD_COLS; + int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + uint16_t xPos = 0; + for (uint8_t i = prevSelectedKey - (prevSelectedKey % KBD_COLS); i < prevSelectedKey; i++) + xPos += keyWidths[i]; + uint16_t keyX = ((xPos * em) >> 4) + (((prevSelectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1)); + uint16_t keyY = row * keyH; + uint16_t keyW = (keyWidths[prevSelectedKey] * em) >> 4; + fillRect(keyX, keyY, keyW, keyH, WHITE); + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[prevSelectedKey], BLACK); + + // Draw newly selected key + row = selectedKey / KBD_COLS; + keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4); + xPos = 0; + for (uint8_t i = selectedKey - (selectedKey % KBD_COLS); i < selectedKey; i++) + xPos += keyWidths[i]; + keyX = ((xPos * em) >> 4) + (((selectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1)); + keyY = row * keyH; + keyW = (keyWidths[selectedKey] * em) >> 4; + fillRect(keyX, keyY, keyW, keyH, BLACK); + drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[selectedKey], WHITE); + } + } + + prevSelectedKey = selectedKey; +} + +// Draw the key label corresponding to the char +// for most keys it draws the character itself +// for ['\b', '\n', ' ', '\x1b'] it draws special glyphs +void InkHUD::KeyboardApplet::drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color) +{ + if (key == '\b') { + // Draw backspace glyph: 13 x 9 px + /** + * [][][][][][][][][] + * [][] [] + * [][] [] [] [] + * [][] [] [] [] + * [][] [] [] + * [][] [] [] [] + * [][] [] [] [] + * [][] [] + * [][][][][][][][][] + */ + const uint8_t bsBitmap[] = {0x0f, 0xf8, 0x18, 0x08, 0x32, 0x28, 0x61, 0x48, 0xc0, + 0x88, 0x61, 0x48, 0x32, 0x28, 0x18, 0x08, 0x0f, 0xf8}; + uint16_t leftPadding = (width - 13) >> 1; + drawBitmap(left + leftPadding, top + 1, bsBitmap, 13, 9, color); + } else if (key == '\n') { + // Draw done glyph: 12 x 9 px + /** + * [][] + * [][] + * [][] + * [][] + * [][] + * [][] [][] + * [][] [][] + * [][][] + * [] + */ + const uint8_t doneBitmap[] = {0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, 0x01, 0x80, 0x03, + 0x00, 0xc6, 0x00, 0x6c, 0x00, 0x38, 0x00, 0x10, 0x00}; + uint16_t leftPadding = (width - 12) >> 1; + drawBitmap(left + leftPadding, top + 1, doneBitmap, 12, 9, color); + } else if (key == ' ') { + // Draw space glyph: 13 x 9 px + /** + * + * + * + * + * [] [] + * [] [] + * [][][][][][][][][][][][][] + * + * + */ + const uint8_t spaceBitmap[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x08, 0x80, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00}; + uint16_t leftPadding = (width - 13) >> 1; + drawBitmap(left + leftPadding, top + 1, spaceBitmap, 13, 9, color); + } else if (key == '\x1b') { + setTextColor(color); + std::string keyText = "ESC"; + uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1; + printAt(left + leftPadding, top, keyText); + } else { + setTextColor(color); + if (key >= 0x61) + key -= 32; // capitalize + std::string keyText = std::string(1, key); + uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1; + printAt(left + leftPadding, top, keyText); + } +} + +void InkHUD::KeyboardApplet::onForeground() +{ + handleInput = true; // Intercept the button input for our applet + + // Select the first key + selectedKey = 0; + prevSelectedKey = 0; +} + +void InkHUD::KeyboardApplet::onBackground() +{ + handleInput = false; +} + +void InkHUD::KeyboardApplet::onButtonShortPress() +{ + char key = keys[selectedKey]; + if (key == '\n') { + inkhud->freeTextDone(); + inkhud->closeKeyboard(); + } else if (key == '\x1b') { + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); + } else { + inkhud->freeText(key); + } +} + +void InkHUD::KeyboardApplet::onButtonLongPress() +{ + char key = keys[selectedKey]; + if (key == '\n') { + inkhud->freeTextDone(); + inkhud->closeKeyboard(); + } else if (key == '\x1b') { + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); + } else { + if (key >= 0x61) + key -= 32; // capitalize + inkhud->freeText(key); + } +} + +void InkHUD::KeyboardApplet::onExitShort() +{ + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); +} + +void InkHUD::KeyboardApplet::onExitLong() +{ + inkhud->freeTextCancel(); + inkhud->closeKeyboard(); +} + +void InkHUD::KeyboardApplet::onNavUp() +{ + if (selectedKey < KBD_COLS) // wrap + selectedKey += KBD_COLS * (KBD_ROWS - 1); + else // move 1 row back + selectedKey -= KBD_COLS; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavDown() +{ + selectedKey += KBD_COLS; + selectedKey %= (KBD_COLS * KBD_ROWS); + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavLeft() +{ + if (selectedKey % KBD_COLS == 0) // wrap + selectedKey += KBD_COLS - 1; + else // move 1 column back + selectedKey--; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +void InkHUD::KeyboardApplet::onNavRight() +{ + if (selectedKey % KBD_COLS == KBD_COLS - 1) // wrap + selectedKey -= KBD_COLS - 1; + else // move 1 column forward + selectedKey++; + + // Request rendering over the previously drawn render + requestUpdate(EInk::UpdateTypes::FAST, false); + // Force an update to bypass lockRequests + inkhud->forceUpdate(EInk::UpdateTypes::FAST); +} + +uint16_t InkHUD::KeyboardApplet::getKeyboardHeight() +{ + const uint16_t keyH = fontSmall.lineHeight() * 1.2; + return keyH * KBD_ROWS; +} +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h new file mode 100644 index 000000000..306a8d8e3 --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/System/Keyboard/KeyboardApplet.h @@ -0,0 +1,66 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +/* + +System Applet to render an on-screeen keyboard + +*/ + +#pragma once + +#include "configuration.h" +#include "graphics/niche/InkHUD/InkHUD.h" +#include "graphics/niche/InkHUD/SystemApplet.h" +#include +namespace NicheGraphics::InkHUD +{ + +class KeyboardApplet : public SystemApplet +{ + public: + KeyboardApplet(); + + void onRender(bool full) override; + void onForeground() override; + void onBackground() override; + void onButtonShortPress() override; + void onButtonLongPress() override; + void onExitShort() override; + void onExitLong() override; + void onNavUp() override; + void onNavDown() override; + void onNavLeft() override; + void onNavRight() override; + + static uint16_t getKeyboardHeight(); // used to set the keyboard tile height + + private: + void drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color); + + static const uint8_t KBD_COLS = 11; + static const uint8_t KBD_ROWS = 4; + + const char keys[KBD_COLS * KBD_ROWS] = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b', // row 0 + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n', // row 1 + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '!', ' ', // row 2 + 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?', '\x1b' // row 3 + }; + + // This array represents the widths of each key in points + // 16 pt = line height of the text + const uint16_t keyWidths[KBD_COLS * KBD_ROWS] = { + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 0 + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 1 + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 2 + 16, 16, 16, 16, 16, 16, 16, 10, 10, 12, 40 // row 3 + }; + + uint16_t rowWidths[KBD_ROWS]; + uint8_t selectedKey = 0; // selected key index + uint8_t prevSelectedKey = 0; +}; + +} // namespace NicheGraphics::InkHUD + +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp index 4b55529bb..b2c58fc60 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.cpp @@ -30,7 +30,7 @@ InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet") // This is then drawn with a FULL refresh by Renderer::begin } -void InkHUD::LogoApplet::onRender() +void InkHUD::LogoApplet::onRender(bool full) { // Size of the region which the logo should "scale to fit" uint16_t logoWLimit = X(0.8); @@ -120,7 +120,7 @@ void InkHUD::LogoApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } // Begin displaying the screen which is shown at shutdown @@ -138,10 +138,10 @@ void InkHUD::LogoApplet::onShutdown() // Intention is to restore display health. inverted = true; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); delay(1000); // Cooldown. Back to back updates aren't great for health. inverted = false; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); delay(1000); // Cooldown // Prepare for the powered-off screen now @@ -176,7 +176,7 @@ void InkHUD::LogoApplet::onReboot() textTitle = "Rebooting..."; fontTitle = fontSmall; - inkhud->forceUpdate(Drivers::EInk::FULL, false); + inkhud->forceUpdate(Drivers::EInk::FULL, true, false); // Perform the update right now, waiting here until complete } diff --git a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h index 37f940453..d70dcc7b2 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Logo/LogoApplet.h @@ -21,7 +21,7 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread { public: LogoApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onShutdown() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h index 7cfb33e9d..7ec76292b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h @@ -19,6 +19,7 @@ namespace NicheGraphics::InkHUD enum MenuAction { NO_ACTION, SEND_PING, + FREE_TEXT, STORE_CANNEDMESSAGE_SELECTION, SEND_CANNEDMESSAGE, SHUTDOWN, diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index b69e31e9a..6a141f73e 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -90,6 +90,8 @@ void InkHUD::MenuApplet::onForeground() OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); OSThread::enabled = true; + freeTextMode = false; + // Upgrade the refresh to FAST, for guaranteed responsiveness inkhud->forceUpdate(EInk::UpdateTypes::FAST); } @@ -116,6 +118,8 @@ void InkHUD::MenuApplet::onBackground() SystemApplet::lockRequests = false; SystemApplet::handleInput = false; + handleFreeText = false; + // Restore the user applet whose tile we borrowed if (borrowedTileOwner) borrowedTileOwner->bringToForeground(); @@ -340,12 +344,26 @@ void InkHUD::MenuApplet::execute(MenuItem item) inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); break; + case FREE_TEXT: + OSThread::enabled = false; + handleFreeText = true; + cm.freeTextItem.rawText.erase(); // clear the previous freetext message + freeTextMode = true; // render input field instead of normal menu + // Open the on-screen keyboard if the joystick is enabled + if (settings->joystick.enabled) + inkhud->openKeyboard(); + break; + case STORE_CANNEDMESSAGE_SELECTION: - cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry + if (!settings->joystick.enabled) + cm.selectedMessageItem = &cm.messageItems.at(cursor - 1); // Minus one: offset for the initial "Send Ping" entry + else + cm.selectedMessageItem = &cm.messageItems.at(cursor - 2); // Minus two: offset for the "Send Ping" and free text entry break; case SEND_CANNEDMESSAGE: cm.selectedRecipientItem = &cm.recipientItems.at(cursor); + // send selected message sendText(cm.selectedRecipientItem->dest, cm.selectedRecipientItem->channelIndex, cm.selectedMessageItem->rawText.c_str()); inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL); // Next refresh should be FULL. Lots of button pressing to get here break; @@ -1373,8 +1391,14 @@ void InkHUD::MenuApplet::showPage(MenuPage page) currentPage = page; } -void InkHUD::MenuApplet::onRender() +void InkHUD::MenuApplet::onRender(bool full) { + // Free text mode draws a text input field and skips the normal rendering + if (freeTextMode) { + drawInputField(0, fontSmall.lineHeight(), X(1.0), Y(1.0) - fontSmall.lineHeight() - 1, cm.freeTextItem.rawText); + return; + } + if (items.size() == 0) LOG_ERROR("Empty Menu"); @@ -1493,44 +1517,48 @@ void InkHUD::MenuApplet::onRender() void InkHUD::MenuApplet::onButtonShortPress() { - // Push the auto-close timer back - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + // Push the auto-close timer back + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (!settings->joystick.enabled) { - if (!cursorShown) { - cursorShown = true; - cursor = 0; - } else { - do { - cursor = (cursor + 1) % items.size(); - } while (items.at(cursor).isHeader); - } - requestUpdate(Drivers::EInk::UpdateTypes::FAST); - } else { - if (cursorShown) - execute(items.at(cursor)); - else - showPage(MenuPage::EXIT); - if (!wantsToRender()) + if (!settings->joystick.enabled) { + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } else { + if (cursorShown) + execute(items.at(cursor)); + else + showPage(MenuPage::EXIT); + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } } void InkHUD::MenuApplet::onButtonLongPress() { - // Push the auto-close timer back - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + // Push the auto-close timer back + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (cursorShown) - execute(items.at(cursor)); - else - showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close + if (cursorShown) + execute(items.at(cursor)); + else + showPage(MenuPage::EXIT); // Special case: Peek at root-menu; longpress again to close - // If we didn't already request a specialized update, when handling a menu action, - // then perform the usual fast update. - // FAST keeps things responsive: important because we're dealing with user input - if (!wantsToRender()) - requestUpdate(Drivers::EInk::UpdateTypes::FAST); + // If we didn't already request a specialized update, when handling a menu action, + // then perform the usual fast update. + // FAST keeps things responsive: important because we're dealing with user input + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } void InkHUD::MenuApplet::onExitShort() @@ -1543,56 +1571,107 @@ void InkHUD::MenuApplet::onExitShort() void InkHUD::MenuApplet::onNavUp() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (!cursorShown) { - cursorShown = true; - cursor = 0; - } else { - do { - if (cursor == 0) - cursor = items.size() - 1; - else - cursor--; - } while (items.at(cursor).isHeader); + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + if (cursor == 0) + cursor = items.size() - 1; + else + cursor--; + } while (items.at(cursor).isHeader); + } + + requestUpdate(Drivers::EInk::UpdateTypes::FAST); } - - requestUpdate(Drivers::EInk::UpdateTypes::FAST); } void InkHUD::MenuApplet::onNavDown() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - if (!cursorShown) { - cursorShown = true; - cursor = 0; - } else { - do { - cursor = (cursor + 1) % items.size(); - } while (items.at(cursor).isHeader); + if (!cursorShown) { + cursorShown = true; + cursor = 0; + } else { + do { + cursor = (cursor + 1) % items.size(); + } while (items.at(cursor).isHeader); + } + + requestUpdate(Drivers::EInk::UpdateTypes::FAST); } - - requestUpdate(Drivers::EInk::UpdateTypes::FAST); } void InkHUD::MenuApplet::onNavLeft() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); - // Go to the previous menu page - showPage(previousPage); - requestUpdate(Drivers::EInk::UpdateTypes::FAST); + // Go to the previous menu page + showPage(previousPage); + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } } void InkHUD::MenuApplet::onNavRight() { - OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (!freeTextMode) { + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + if (cursorShown) + execute(items.at(cursor)); + if (!wantsToRender()) + requestUpdate(Drivers::EInk::UpdateTypes::FAST); + } +} - if (cursorShown) - execute(items.at(cursor)); - if (!wantsToRender()) - requestUpdate(Drivers::EInk::UpdateTypes::FAST); +void InkHUD::MenuApplet::onFreeText(char c) +{ + if (cm.freeTextItem.rawText.length() >= menuTextLimit && c != '\b') + return; + if (c == '\b') { + if (!cm.freeTextItem.rawText.empty()) + cm.freeTextItem.rawText.pop_back(); + } else { + cm.freeTextItem.rawText += c; + } + requestUpdate(Drivers::EInk::UpdateTypes::FAST); +} + +void InkHUD::MenuApplet::onFreeTextDone() +{ + // Restart the auto-close timeout + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + OSThread::enabled = true; + + handleFreeText = false; + freeTextMode = false; + + if (!cm.freeTextItem.rawText.empty()) { + cm.selectedMessageItem = &cm.freeTextItem; + showPage(MenuPage::CANNEDMESSAGE_RECIPIENT); + } + requestUpdate(Drivers::EInk::UpdateTypes::FAST); +} + +void InkHUD::MenuApplet::onFreeTextCancel() +{ + // Restart the auto-close timeout + OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL); + OSThread::enabled = true; + + handleFreeText = false; + freeTextMode = false; + + // Clear the free text message + cm.freeTextItem.rawText.erase(); + + requestUpdate(Drivers::EInk::UpdateTypes::FAST); } // Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu @@ -1647,6 +1726,10 @@ void InkHUD::MenuApplet::populateSendPage() // Position / NodeInfo packet items.push_back(MenuItem("Ping", MenuAction::SEND_PING, MenuPage::EXIT)); + // If joystick is available, include the Free Text option + if (settings->joystick.enabled) + items.push_back(MenuItem("Free Text", MenuAction::FREE_TEXT, MenuPage::SEND)); + // One menu item for each canned message uint8_t count = cm.store->size(); for (uint8_t i = 0; i < count; i++) { @@ -1746,6 +1829,48 @@ void InkHUD::MenuApplet::populateRecipientPage() items.push_back(MenuItem("Exit", MenuPage::EXIT)); } +void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, std::string text) +{ + setFont(fontSmall); + uint16_t wrapMaxH = 0; + + // Draw the text, input box, and cursor + // Adjusting the box for screen height + while (wrapMaxH < height - fontSmall.lineHeight()) { + wrapMaxH += fontSmall.lineHeight(); + } + + // If the text is so long that it goes outside of the input box, the text is actually rendered off screen. + uint32_t textHeight = getWrappedTextHeight(0, width - 5, text); + if (!text.empty()) { + uint16_t textPadding = X(1.0) > Y(1.0) ? wrapMaxH - textHeight : wrapMaxH - textHeight + 1; + if (textHeight > wrapMaxH) + printWrapped(2, textPadding, width - 5, text); + else + printWrapped(2, top + 2, width - 5, text); + } + + uint16_t textCursorX = text.empty() ? 1 : getCursorX(); + uint16_t textCursorY = text.empty() ? fontSmall.lineHeight() + 2 : getCursorY() - fontSmall.lineHeight() + 3; + + if (textCursorX + 1 > width - 5) { + textCursorX = getCursorX() - width + 5; + textCursorY += fontSmall.lineHeight(); + } + + fillRect(textCursorX + 1, textCursorY, 1, fontSmall.lineHeight(), BLACK); + + // A white rectangle clears the top part of the screen for any text that's printed beyond the input box + fillRect(0, 0, X(1.0), top, WHITE); + + // Draw character limit + std::string ftlen = std::to_string(text.length()) + "/" + to_string(menuTextLimit); + uint16_t textLen = getTextWidth(ftlen); + printAt(X(1.0) - textLen - 2, 0, ftlen); + + // Draw the border + drawRect(0, top, width, wrapMaxH + 5, BLACK); +} // Renders the panel shown at the top of the root menu. // Displays the clock, and several other pieces of instantaneous system info, // which we'd prefer not to have displayed in a normal applet, as they update too frequently. @@ -1887,4 +2012,4 @@ void InkHUD::MenuApplet::freeCannedMessageResources() cm.messageItems.clear(); cm.recipientItems.clear(); } -#endif // MESHTASTIC_INCLUDE_INKHUD \ No newline at end of file +#endif // MESHTASTIC_INCLUDE_INKHUD diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h index 82ccc8f45..7b092153b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h @@ -32,7 +32,10 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void onNavDown() override; void onNavLeft() override; void onNavRight() override; - void onRender() override; + void onFreeText(char c) override; + void onFreeTextDone() override; + void onFreeTextCancel() override; + void onRender(bool full) override; void show(Tile *t); // Open the menu, onto a user tile void setStartPage(MenuPage page); @@ -51,6 +54,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds + void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, + std::string text); // Draw input field for free text uint16_t getSystemInfoPanelHeight(); void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width, uint16_t *height = nullptr); // Info panel at top of root menu @@ -62,8 +67,9 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread MenuPage previousPage = MenuPage::EXIT; uint8_t cursor = 0; // Which menu item is currently highlighted bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection) - + bool freeTextMode = false; uint16_t systemInfoPanelHeight = 0; // Need to know before we render + uint16_t menuTextLimit = 200; std::vector items; // MenuItems for the current page. Filled by ShowPage std::vector nodeConfigLabels; // Persistent labels for Node Config pages @@ -104,6 +110,8 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread // Cleared onBackground (when MenuApplet closes) std::vector messageItems; std::vector recipientItems; + + MessageItem freeTextItem; } cm; Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp index 2ea9c7fe0..19cef4fbd 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp @@ -65,7 +65,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket return 0; } -void InkHUD::NotificationApplet::onRender() +void InkHUD::NotificationApplet::onRender(bool full) { // Clear the region beneath the tile // Most applets are drawing onto an empty frame buffer and don't need to do this @@ -139,54 +139,47 @@ void InkHUD::NotificationApplet::onForeground() void InkHUD::NotificationApplet::onBackground() { handleInput = false; + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } void InkHUD::NotificationApplet::onButtonShortPress() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onButtonLongPress() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onExitShort() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onExitLong() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavUp() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavDown() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavLeft() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } void InkHUD::NotificationApplet::onNavRight() { dismiss(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } // Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h index 16ea13407..d398a36f3 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.h @@ -26,7 +26,7 @@ class NotificationApplet : public SystemApplet public: NotificationApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonShortPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp index 09931f109..a09ff55d5 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp @@ -9,7 +9,7 @@ InkHUD::PairingApplet::PairingApplet() bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); } -void InkHUD::PairingApplet::onRender() +void InkHUD::PairingApplet::onRender(bool full) { // Header setFont(fontMedium); @@ -45,7 +45,7 @@ void InkHUD::PairingApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status) diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h index b89783a25..4c2e95321 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.h @@ -22,7 +22,7 @@ class PairingApplet : public SystemApplet public: PairingApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; diff --git a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp index 99cdeb0ac..228c8b2ca 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.cpp @@ -4,7 +4,7 @@ using namespace NicheGraphics; -void InkHUD::PlaceholderApplet::onRender() +void InkHUD::PlaceholderApplet::onRender(bool full) { // This placeholder applet fills its area with sparse diagonal lines hatchRegion(0, 0, width(), height(), 8, BLACK); diff --git a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h index 78ba5cd89..fa40913e0 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Placeholder/PlaceholderApplet.h @@ -17,7 +17,7 @@ namespace NicheGraphics::InkHUD class PlaceholderApplet : public SystemApplet { public: - void onRender() override; + void onRender(bool full) override; // Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet. // The window manager decides when and where it should be rendered diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp index 7869319fe..6cac2644b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.cpp @@ -45,7 +45,7 @@ InkHUD::TipsApplet::TipsApplet() bringToForeground(); } -void InkHUD::TipsApplet::onRender() +void InkHUD::TipsApplet::onRender(bool full) { switch (tipQueue.front()) { case Tip::WELCOME: @@ -261,7 +261,7 @@ void InkHUD::TipsApplet::onBackground() // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case - inkhud->forceUpdate(EInk::UpdateTypes::FULL); + inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); } // While our SystemApplet::handleInput flag is true @@ -292,9 +292,8 @@ void InkHUD::TipsApplet::onButtonShortPress() inkhud->persistence->saveSettings(); } - // Close applet and clean the screen + // Close applet sendToBackground(); - inkhud->forceUpdate(EInk::UpdateTypes::FULL); } else { requestUpdate(); } @@ -306,4 +305,4 @@ void InkHUD::TipsApplet::onExitShort() onButtonShortPress(); } -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h index ff7eea046..2e81d678b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Tips/TipsApplet.h @@ -33,7 +33,7 @@ class TipsApplet : public SystemApplet public: TipsApplet(); - void onRender() override; + void onRender(bool full) override; void onForeground() override; void onBackground() override; void onButtonShortPress() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp index 7c6232f3b..96c519599 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.cpp @@ -34,7 +34,7 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket * return 0; } -void InkHUD::AllMessageApplet::onRender() +void InkHUD::AllMessageApplet::onRender(bool full) { // Find newest message, regardless of whether DM or broadcast MessageStore::Message *message; diff --git a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h index c74e16196..4aa97e4f1 100644 --- a/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h @@ -30,7 +30,7 @@ class Applet; class AllMessageApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp index a3b9615a5..189a56cab 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.cpp @@ -37,7 +37,7 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p) return 0; } -void InkHUD::DMApplet::onRender() +void InkHUD::DMApplet::onRender(bool full) { // Abort if no text message if (!latestMessage->dm.sender) { diff --git a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h index b3dc36e66..4eb0ec704 100644 --- a/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/DM/DMApplet.h @@ -30,7 +30,7 @@ class Applet; class DMApplet : public Applet { public: - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp index ad0f9fc47..ae7679962 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp @@ -5,10 +5,10 @@ using namespace NicheGraphics; -void InkHUD::PositionsApplet::onRender() +void InkHUD::PositionsApplet::onRender(bool full) { // Draw the usual map applet first - MapApplet::onRender(); + MapApplet::onRender(full); // Draw our latest "node of interest" as a special marker // ------------------------------------------------------- diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h index 28a53cb0f..d0d3e5f07 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h @@ -24,7 +24,7 @@ class PositionsApplet : public MapApplet, public SinglePortModule { public: PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {} - void onRender() override; + void onRender(bool full) override; protected: ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp index fdb5a168d..f16721357 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp @@ -22,7 +22,7 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex) store = new MessageStore("ch" + to_string(channelIndex)); } -void InkHUD::ThreadedMessageApplet::onRender() +void InkHUD::ThreadedMessageApplet::onRender(bool full) { // ============= // Draw a header diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h index c986539b3..045e2a6fc 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h @@ -36,7 +36,7 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule explicit ThreadedMessageApplet(uint8_t channelIndex); ThreadedMessageApplet() = delete; - void onRender() override; + void onRender(bool full) override; void onActivate() override; void onDeactivate() override; diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index fa45a49ed..e6c16d350 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -238,6 +238,39 @@ void InkHUD::Events::onNavRight() } } +void InkHUD::Events::onFreeText(char c) +{ + // Trigger the first system applet that wants to handle the new character + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeText(c); + break; + } + } +} + +void InkHUD::Events::onFreeTextDone() +{ + // Trigger the first system applet that wants to handle it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeTextDone(); + break; + } + } +} + +void InkHUD::Events::onFreeTextCancel() +{ + // Trigger the first system applet that wants to handle it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + sa->onFreeTextCancel(); + break; + } + } +} + // Callback for deepSleepObserver // Returns 0 to signal that we agree to sleep now int InkHUD::Events::beforeDeepSleep(void *unused) @@ -266,7 +299,7 @@ int InkHUD::Events::beforeDeepSleep(void *unused) // then prepared a final powered-off screen for us, which shows device shortname. // We're updating to show that one now. - inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false); + inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false); delay(1000); // Cooldown, before potentially yanking display power // InkHUD shutdown complete diff --git a/src/graphics/niche/InkHUD/Events.h b/src/graphics/niche/InkHUD/Events.h index 1916cf78e..873f53fd5 100644 --- a/src/graphics/niche/InkHUD/Events.h +++ b/src/graphics/niche/InkHUD/Events.h @@ -37,6 +37,11 @@ class Events void onNavLeft(); // Navigate left void onNavRight(); // Navigate right + // Free text typing events + void onFreeText(char c); // New freetext character input + void onFreeTextDone(); + void onFreeTextCancel(); + int beforeDeepSleep(void *unused); // Prepare for shutdown int beforeReboot(void *unused); // Prepare for reboot int onReceiveTextMessage(const meshtastic_MeshPacket *packet); // Store most recent text message diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD/InkHUD.cpp index 13b15b7e8..5fab67639 100644 --- a/src/graphics/niche/InkHUD/InkHUD.cpp +++ b/src/graphics/niche/InkHUD/InkHUD.cpp @@ -175,6 +175,25 @@ void InkHUD::InkHUD::navRight() } } +// Call this for keyboard input +// The Keyboard Applet also calls this +void InkHUD::InkHUD::freeText(char c) +{ + events->onFreeText(c); +} + +// Call this to complete a freetext input +void InkHUD::InkHUD::freeTextDone() +{ + events->onFreeTextDone(); +} + +// Call this to cancel a freetext input +void InkHUD::InkHUD::freeTextCancel() +{ + events->onFreeTextCancel(); +} + // Cycle the next user applet to the foreground // Only activated applets are cycled // If user has a multi-applet layout, the applets will cycle on the "focused tile" @@ -204,6 +223,18 @@ void InkHUD::InkHUD::openAlignStick() windowManager->openAlignStick(); } +// Open the on-screen keyboard +void InkHUD::InkHUD::openKeyboard() +{ + windowManager->openKeyboard(); +} + +// Close the on-screen keyboard +void InkHUD::InkHUD::closeKeyboard() +{ + windowManager->closeKeyboard(); +} + // In layouts where multiple applets are shown at once, change which tile is focused // The focused tile in the one which cycles applets on button short press, and displays menu on long press void InkHUD::InkHUD::nextTile() @@ -252,10 +283,11 @@ void InkHUD::InkHUD::requestUpdate() // Ignores all diplomacy: // - the display *will* update // - the specified update type *will* be used +// If the all parameter is true, the whole screen buffer is cleared and re-rendered // If the async parameter is false, code flow is blocked while the update takes place -void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async) +void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool all, bool async) { - renderer->forceUpdate(type, async); + renderer->forceUpdate(type, all, async); } // Wait for any in-progress display update to complete before continuing diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD/InkHUD.h index 5280d9ac7..ae029137e 100644 --- a/src/graphics/niche/InkHUD/InkHUD.h +++ b/src/graphics/niche/InkHUD/InkHUD.h @@ -63,6 +63,11 @@ class InkHUD void navLeft(); void navRight(); + // Freetext handlers + void freeText(char c); + void freeTextDone(); + void freeTextCancel(); + // Trigger UI changes // - called by various InkHUD components // - suitable(?) for use by aux button, connected in variant nicheGraphics.h @@ -71,6 +76,8 @@ class InkHUD void prevApplet(); void openMenu(); void openAlignStick(); + void openKeyboard(); + void closeKeyboard(); void nextTile(); void prevTile(); void rotate(); @@ -84,7 +91,8 @@ class InkHUD // - called by various InkHUD components void requestUpdate(); - void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true); + void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false, + bool async = true); void awaitUpdate(); // (Re)configuring WindowManager diff --git a/src/graphics/niche/InkHUD/Renderer.cpp b/src/graphics/niche/InkHUD/Renderer.cpp index 072e9dbd6..89a83c932 100644 --- a/src/graphics/niche/InkHUD/Renderer.cpp +++ b/src/graphics/niche/InkHUD/Renderer.cpp @@ -56,15 +56,16 @@ void InkHUD::Renderer::setDisplayResilience(uint8_t fastPerFull, float stressMul void InkHUD::Renderer::begin() { - forceUpdate(Drivers::EInk::UpdateTypes::FULL, false); + forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false); } // Set a flag, which will be picked up by runOnce, ASAP. // Quite likely, multiple applets will all want to respond to one event (Observable, etc) // Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce -void InkHUD::Renderer::requestUpdate() +void InkHUD::Renderer::requestUpdate(bool all) { requested = true; + renderAll |= all; // We will run the thread as soon as we loop(), // after all Applets have had a chance to observe whatever event set this off @@ -79,10 +80,11 @@ void InkHUD::Renderer::requestUpdate() // Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event // Display health, for example. // In these situations, we use forceUpdate -void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool async) +void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool all, bool async) { requested = true; forced = true; + renderAll |= all; displayHealth.forceUpdateType(type); // Normally, we need to start the timer, in case the display is busy and we briefly defer the update @@ -219,7 +221,8 @@ void InkHUD::Renderer::render(bool async) Drivers::EInk::UpdateTypes updateType = decideUpdateType(); // Render the new image - clearBuffer(); + if (renderAll) + clearBuffer(); renderUserApplets(); renderPlaceholders(); renderSystemApplets(); @@ -247,6 +250,7 @@ void InkHUD::Renderer::render(bool async) // Tidy up, ready for a new request requested = false; forced = false; + renderAll = false; } // Manually fill the image buffer with WHITE @@ -259,6 +263,76 @@ void InkHUD::Renderer::clearBuffer() memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth); } +// Manually clear the pixels below a tile +void InkHUD::Renderer::clearTile(Tile *t) +{ + // Rotate the tile dimensions + int16_t left = 0; + int16_t top = 0; + uint16_t width = 0; + uint16_t height = 0; + switch (settings->rotation) { + case 0: + left = t->getLeft(); + top = t->getTop(); + width = t->getWidth(); + height = t->getHeight(); + break; + case 1: + left = driver->width - (t->getTop() + t->getHeight()); + top = t->getLeft(); + width = t->getHeight(); + height = t->getWidth(); + break; + case 2: + left = driver->width - (t->getLeft() + t->getWidth()); + top = driver->height - (t->getTop() + t->getHeight()); + width = t->getWidth(); + height = t->getHeight(); + break; + case 3: + left = t->getTop(); + top = driver->height - (t->getLeft() + t->getWidth()); + width = t->getHeight(); + height = t->getWidth(); + break; + } + + // Calculate the bounds to clear + uint16_t xStart = (left < 0) ? 0 : left; + uint16_t yStart = (top < 0) ? 0 : top; + if (xStart >= driver->width || yStart >= driver->height || left + width < 0 || top + height < 0) + return; // the box is completely off the screen + uint16_t xEnd = left + width; + uint16_t yEnd = top + height; + if (xEnd > driver->width) + xEnd = driver->width; + if (yEnd > driver->height) + yEnd = driver->height; + + // Clear the pixels + if (xStart == 0 && xEnd == driver->width) { // full width box is easier to clear + memset(imageBuffer + (yStart * imageBufferWidth), 0xFF, (yEnd - yStart) * imageBufferWidth); + } else { + const uint16_t byteStart = (xStart / 8) + 1; + const uint16_t byteEnd = xEnd / 8; + const uint8_t leadingByte = 0xFF >> (xStart - ((byteStart - 1) * 8)); + const uint8_t trailingByte = (0xFF00 >> (xEnd - (byteEnd * 8))) & 0xFF; + for (uint16_t i = yStart * imageBufferWidth; i < yEnd * imageBufferWidth; i += imageBufferWidth) { + // Set the leading byte + imageBuffer[i + byteStart - 1] |= leadingByte; + + // Set the continuous bytes + if (byteStart < byteEnd) + memset(imageBuffer + i + byteStart, 0xFF, byteEnd - byteStart); + + // Set the trailing byte + if (byteEnd != imageBufferWidth) + imageBuffer[i + byteEnd] |= trailingByte; + } + } +} + void InkHUD::Renderer::checkLocks() { lockRendering = nullptr; @@ -323,12 +397,12 @@ Drivers::EInk::UpdateTypes InkHUD::Renderer::decideUpdateType() if (!forced) { // User applets for (Applet *ua : inkhud->userApplets) { - if (ua && ua->isForeground()) + if (ua && ua->isForeground() && (ua->wantsToRender() || renderAll)) displayHealth.requestUpdateType(ua->wantsUpdateType()); } // System Applets for (SystemApplet *sa : inkhud->systemApplets) { - if (sa && sa->isForeground()) + if (sa && sa->isForeground() && (sa->wantsToRender() || sa->alwaysRender || renderAll)) displayHealth.requestUpdateType(sa->wantsUpdateType()); } } @@ -346,9 +420,16 @@ void InkHUD::Renderer::renderUserApplets() // Render any user applets which are currently visible for (Applet *ua : inkhud->userApplets) { - if (ua && ua->isActive() && ua->isForeground()) { + if (ua && ua->isActive() && ua->isForeground() && (ua->wantsToRender() || renderAll)) { + + // Clear the tile unless the applet wants to draw over its previous render + // or everything is getting re-rendered anyways + if (ua->wantsFullRender() && !renderAll) + clearTile(ua->getTile()); + uint32_t start = millis(); - ua->render(); // Draw! + bool full = ua->wantsFullRender() || renderAll; + ua->render(full); // Draw! uint32_t stop = millis(); LOG_DEBUG("%s took %dms to render", ua->name, stop - start); } @@ -370,6 +451,9 @@ void InkHUD::Renderer::renderSystemApplets() if (!sa->isForeground()) continue; + if (!sa->wantsToRender() && !sa->alwaysRender && !renderAll) + continue; + // Skip if locked by another applet if (lockRendering && lockRendering != sa) continue; @@ -381,8 +465,14 @@ void InkHUD::Renderer::renderSystemApplets() assert(sa->getTile()); + // Clear the tile unless the applet wants to draw over its previous render + // or everything is getting re-rendered anyways + if (sa->wantsFullRender() && !renderAll) + clearTile(sa->getTile()); + // uint32_t start = millis(); - sa->render(); // Draw! + bool full = sa->wantsFullRender() || renderAll; + sa->render(full); // Draw! // uint32_t stop = millis(); // LOG_DEBUG("%s took %dms to render", sa->name, stop - start); } @@ -409,7 +499,10 @@ void InkHUD::Renderer::renderPlaceholders() // uint32_t start = millis(); for (Tile *t : emptyTiles) { t->assignApplet(placeholder); - placeholder->render(); + // Clear the tile unless everything is getting re-rendered + if (!renderAll) + clearTile(t); + placeholder->render(true); // full render t->assignApplet(nullptr); } // uint32_t stop = millis(); diff --git a/src/graphics/niche/InkHUD/Renderer.h b/src/graphics/niche/InkHUD/Renderer.h index b6cf9e215..5cfb79277 100644 --- a/src/graphics/niche/InkHUD/Renderer.h +++ b/src/graphics/niche/InkHUD/Renderer.h @@ -37,8 +37,8 @@ class Renderer : protected concurrency::OSThread // Call these to make the image change - void requestUpdate(); // Update display, if a foreground applet has info it wants to show - void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, + void requestUpdate(bool all = false); // Update display, if a foreground applet has info it wants to show + void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false, bool async = true); // Update display, regardless of whether any applets requested this // Wait for an update to complete @@ -65,6 +65,7 @@ class Renderer : protected concurrency::OSThread // Steps of the rendering process void clearBuffer(); + void clearTile(Tile *t); void checkLocks(); bool shouldUpdate(); Drivers::EInk::UpdateTypes decideUpdateType(); @@ -85,6 +86,7 @@ class Renderer : protected concurrency::OSThread bool requested = false; bool forced = false; + bool renderAll = false; // For convenience InkHUD *inkhud = nullptr; diff --git a/src/graphics/niche/InkHUD/SystemApplet.h b/src/graphics/niche/InkHUD/SystemApplet.h index fb5b06e51..32e0e58bb 100644 --- a/src/graphics/niche/InkHUD/SystemApplet.h +++ b/src/graphics/niche/InkHUD/SystemApplet.h @@ -22,9 +22,11 @@ class SystemApplet : public Applet public: // System applets have the right to: - bool handleInput = false; // - respond to input from the user button - bool lockRendering = false; // - prevent other applets from being rendered during an update - bool lockRequests = false; // - prevent other applets from triggering display updates + bool handleInput = false; // - respond to input from the user button + bool handleFreeText = false; // - respond to free text input + bool lockRendering = false; // - prevent other applets from being rendered during an update + bool lockRequests = false; // - prevent other applets from triggering display updates + bool alwaysRender = false; // - render every time the screen is updated virtual void onReboot() { onShutdown(); } // - handle reboot specially virtual void onApplyingChanges() {} @@ -41,4 +43,4 @@ class SystemApplet : public Applet }; // namespace NicheGraphics::InkHUD -#endif \ No newline at end of file +#endif diff --git a/src/graphics/niche/InkHUD/Tile.cpp b/src/graphics/niche/InkHUD/Tile.cpp index 5e548de74..8beb25f39 100644 --- a/src/graphics/niche/InkHUD/Tile.cpp +++ b/src/graphics/niche/InkHUD/Tile.cpp @@ -18,7 +18,7 @@ static int32_t runtaskHighlight() LOG_DEBUG("Dismissing Highlight"); InkHUD::Tile::highlightShown = false; InkHUD::Tile::highlightTarget = nullptr; - InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST); // Re-render, clearing the highlighting + InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); // Re-render, clearing the highlighting return taskHighlight->disable(); } static void inittaskHighlight() @@ -190,6 +190,18 @@ void InkHUD::Tile::handleAppletPixel(int16_t x, int16_t y, Color c) } } +// Used in Renderer for clearing the tile +int16_t InkHUD::Tile::getLeft() +{ + return left; +} + +// Used in Renderer for clearing the tile +int16_t InkHUD::Tile::getTop() +{ + return top; +} + // Called by Applet base class, when setting applet dimensions, immediately before render uint16_t InkHUD::Tile::getWidth() { @@ -220,7 +232,7 @@ void InkHUD::Tile::requestHighlight() { Tile::highlightTarget = this; Tile::highlightShown = false; - inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST); + inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); } // Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first diff --git a/src/graphics/niche/InkHUD/Tile.h b/src/graphics/niche/InkHUD/Tile.h index 0f5444f17..0c09e4704 100644 --- a/src/graphics/niche/InkHUD/Tile.h +++ b/src/graphics/niche/InkHUD/Tile.h @@ -29,6 +29,8 @@ class Tile void setRegion(uint8_t layoutSize, uint8_t tileIndex); // Assign region automatically, based on layout void setRegion(int16_t left, int16_t top, uint16_t width, uint16_t height); // Assign region manually void handleAppletPixel(int16_t x, int16_t y, Color c); // Receive px output from assigned applet + int16_t getLeft(); + int16_t getTop(); uint16_t getWidth(); uint16_t getHeight(); static uint16_t maxDisplayDimension(); // Largest possible width / height any tile may ever encounter diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index 0548de1eb..9c18fbd48 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -4,6 +4,7 @@ #include "./Applets/System/AlignStick/AlignStickApplet.h" #include "./Applets/System/BatteryIcon/BatteryIconApplet.h" +#include "./Applets/System/Keyboard/KeyboardApplet.h" #include "./Applets/System/Logo/LogoApplet.h" #include "./Applets/System/Menu/MenuApplet.h" #include "./Applets/System/Notification/NotificationApplet.h" @@ -148,6 +149,28 @@ void InkHUD::WindowManager::openAlignStick() } } +void InkHUD::WindowManager::openKeyboard() +{ + KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard"); + + if (keyboard) { + keyboard->bringToForeground(); + keyboardOpen = true; + changeLayout(); + } +} + +void InkHUD::WindowManager::closeKeyboard() +{ + KeyboardApplet *keyboard = (KeyboardApplet *)inkhud->getSystemApplet("Keyboard"); + + if (keyboard) { + keyboard->sendToBackground(); + keyboardOpen = false; + changeLayout(); + } +} + // On the currently focussed tile: cycle to the next available user applet // Applets available for this must be activated, and not already displayed on another tile void InkHUD::WindowManager::nextApplet() @@ -272,7 +295,6 @@ void InkHUD::WindowManager::toggleBatteryIcon() batteryIcon->sendToBackground(); // Force-render - // - redraw all applets inkhud->forceUpdate(EInk::UpdateTypes::FAST); } @@ -311,9 +333,25 @@ void InkHUD::WindowManager::changeLayout() menu->show(ft); } + // Resize for the on-screen keyboard + if (keyboardOpen) { + // Send all user applets to the background + // User applets currently don't handle free text input + for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) + inkhud->userApplets.at(i)->sendToBackground(); + // Find the first system applet that can handle freetext and resize it + for (SystemApplet *sa : inkhud->systemApplets) { + if (sa->handleFreeText) { + const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight(); + sa->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height() - keyboardHeight - 1); + break; + } + } + } + // Force-render // - redraw all applets - inkhud->forceUpdate(EInk::UpdateTypes::FAST); + inkhud->forceUpdate(EInk::UpdateTypes::FAST, true); } // Perform necessary reconfiguration when user activates or deactivates applets at run-time @@ -347,7 +385,7 @@ void InkHUD::WindowManager::changeActivatedApplets() // Force-render // - redraw all applets - inkhud->forceUpdate(EInk::UpdateTypes::FAST); + inkhud->forceUpdate(EInk::UpdateTypes::FAST, true); } // Some applets may be permitted to bring themselves to foreground, to show new data @@ -433,8 +471,10 @@ void InkHUD::WindowManager::createSystemApplets() addSystemApplet("Logo", new LogoApplet, new Tile); addSystemApplet("Pairing", new PairingApplet, new Tile); addSystemApplet("Tips", new TipsApplet, new Tile); - if (settings->joystick.enabled) + if (settings->joystick.enabled) { addSystemApplet("AlignStick", new AlignStickApplet, new Tile); + addSystemApplet("Keyboard", new KeyboardApplet, new Tile); + } addSystemApplet("Menu", new MenuApplet, nullptr); @@ -457,9 +497,13 @@ void InkHUD::WindowManager::placeSystemTiles() inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); - if (settings->joystick.enabled) + if (settings->joystick.enabled) { inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height()); - + const uint16_t keyboardHeight = KeyboardApplet::getKeyboardHeight(); + inkhud->getSystemApplet("Keyboard") + ->getTile() + ->setRegion(0, inkhud->height() - keyboardHeight, inkhud->width(), keyboardHeight); + } inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20); const uint16_t batteryIconHeight = Applet::getHeaderHeight() - 2 - 2; diff --git a/src/graphics/niche/InkHUD/WindowManager.h b/src/graphics/niche/InkHUD/WindowManager.h index 5def48f8c..948ef6131 100644 --- a/src/graphics/niche/InkHUD/WindowManager.h +++ b/src/graphics/niche/InkHUD/WindowManager.h @@ -31,6 +31,8 @@ class WindowManager void prevTile(); void openMenu(); void openAlignStick(); + void openKeyboard(); + void closeKeyboard(); void nextApplet(); void prevApplet(); void rotate(); @@ -64,6 +66,7 @@ class WindowManager void findOrphanApplets(); // Find any applets left-behind when layout changes std::vector userTiles; // Tiles which can host user applets + bool keyboardOpen = false; // For convenience InkHUD *inkhud = nullptr; diff --git a/src/graphics/niche/InkHUD/docs/README.md b/src/graphics/niche/InkHUD/docs/README.md index aa23f065f..8c30aba58 100644 --- a/src/graphics/niche/InkHUD/docs/README.md +++ b/src/graphics/niche/InkHUD/docs/README.md @@ -174,7 +174,7 @@ class BasicExampleApplet : public Applet // You must have an onRender() method // All drawing happens here - void onRender() override; + void onRender(bool full) override; }; ``` @@ -183,7 +183,7 @@ The `onRender` method is called when the display image is redrawn. This can happ ```cpp // All drawing happens here // Our basic example doesn't do anything useful. It just passively prints some text. -void InkHUD::BasicExampleApplet::onRender() +void InkHUD::BasicExampleApplet::onRender(bool full) { printAt(0, 0, "Hello, world!"); } From 8f630bfcf3f38fdc1e13a2c6f36f1331c4feabfa Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 12:52:17 -0600 Subject: [PATCH 070/223] Fix typo in LED state comment And removed unused define --- variants/nrf52840/meshlink/variant.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/variants/nrf52840/meshlink/variant.h b/variants/nrf52840/meshlink/variant.h index 9075d0467..00107ac34 100644 --- a/variants/nrf52840/meshlink/variant.h +++ b/variants/nrf52840/meshlink/variant.h @@ -32,8 +32,7 @@ extern "C" { #define PIN_LED1 (24) // Built in white led for status #define LED_BLUE PIN_LED1 -#define LED_STATE_ON 0 // State when LED is litted -#define LED_INVERTED 1 +#define LED_STATE_ON 0 // State when LED is lit // Testing USB detection // #define NRF_APM @@ -150,4 +149,4 @@ static const uint8_t SCK = PIN_SPI_SCK; /*---------------------------------------------------------------------------- * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif \ No newline at end of file +#endif From 004179c0453f53884475740c702652fa3ae84cfc Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 30 Jan 2026 13:46:11 -0600 Subject: [PATCH 071/223] Fix OTA filename determination to use unified format for ESP32 (#9488) --- bin/device-install.bat | 12 ++---------- bin/device-install.sh | 10 ++-------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/bin/device-install.bat b/bin/device-install.bat index c200a3201..69469d581 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -156,16 +156,8 @@ IF %BPS_RESET% EQU 1 ( SET "PROGNAME=!FILENAME:.factory.bin=!" CALL :LOG_MESSAGE DEBUG "Computed PROGNAME: !PROGNAME!" -IF "__!MCU!__" == "__esp32s3__" ( - @REM We are working with ESP32-S3 - SET "OTA_FILENAME=bleota-s3.bin" -) ELSE IF "__!MCU!__" == "__esp32c3__" ( - @REM We are working with ESP32-C3 - SET "OTA_FILENAME=bleota-c3.bin" -) ELSE ( - @REM Everything else - SET "OTA_FILENAME=bleota.bin" -) +@REM Determine OTA filename based on MCU type (unified OTA format) +SET "OTA_FILENAME=mt-!MCU!-ota.bin" CALL :LOG_MESSAGE DEBUG "Set OTA_FILENAME to: !OTA_FILENAME!" @REM Set SPIFFS filename with "littlefs-" prefix. diff --git a/bin/device-install.sh b/bin/device-install.sh index 49427524e..52a848c38 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -131,14 +131,8 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then exit 1 fi - # Determine OTA filename based on MCU type - if [ "$MCU" == "esp32s3" ]; then - OTAFILE=bleota-s3.bin - elif [ "$MCU" == "esp32c3" ]; then - OTAFILE=bleota-c3.bin - else - OTAFILE=bleota.bin - fi + # Determine OTA filename based on MCU type (unified OTA format) + OTAFILE="mt-${MCU}-ota.bin" # Set SPIFFS filename with "littlefs-" prefix. SPIFFSFILE="littlefs-${PROGNAME/firmware-/}.bin" From 7b03980e0a02f55aaf041159eb6d7649ffc8e967 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 30 Jan 2026 13:46:24 -0600 Subject: [PATCH 072/223] Refuse to send legacy DMs simply because the remote public key is unknown (#9485) * Refuse to send legacy DMs simply because the remote public key is unknown * Update src/mesh/Router.cpp - use more specific error Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/mesh/Router.cpp - more detail in warning message Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mesh/Router.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 287fbcf60..32544a051 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -620,15 +620,19 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) !(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 || strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) && // Check for valid keys and single node destination - config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr && - // Check for a known public key for the destination - (node->user.public_key.size == 32) && + config.security.private_key.size == 32 && !isBroadcast(p->to) && // Some portnums either make no sense to send with PKC p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) { LOG_DEBUG("Use PKI!"); if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN) return meshtastic_Routing_Error_TOO_LARGE; + // Check for a known public key for the destination + if (node == nullptr || node->user.public_key.size != 32) { + LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to, + p->decoded.portnum); + return meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY; + } if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) && memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) { LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes, From 1d30342c0035eb03da99e65abe312c7682c2e2d8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 31 Jan 2026 12:15:06 -0600 Subject: [PATCH 073/223] Don't ever define PIN_LED or BLE_LED_INVERTED (#9494) * Don't ever define PIN_LED * Deprecate BLE_LED_INVERTED --- src/BluetoothStatus.h | 12 ++---------- src/main.cpp | 10 ++++------ src/nimble/NimbleBluetooth.cpp | 6 +----- variants/esp32/wiphone/variant.h | 1 - variants/esp32s3/esp32-s3-pico/variant.h | 1 - .../diy/seeed-xiao-nrf52840-wio-sx1262/variant.h | 2 -- variants/nrf52840/t-echo-lite/variant.h | 1 - variants/rp2040/challenger_2040_lora/pins_arduino.h | 2 +- variants/rp2040/challenger_2040_lora/variant.h | 2 -- variants/rp2040/rak11310/pins_arduino.h | 3 +-- variants/rp2040/rak11310/variant.h | 2 +- variants/rp2040/senselora_rp2040/pins_arduino.h | 3 +-- variants/rp2040/senselora_rp2040/variant.h | 2 +- 13 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/BluetoothStatus.h b/src/BluetoothStatus.h index 680aec929..4ea4a95ac 100644 --- a/src/BluetoothStatus.h +++ b/src/BluetoothStatus.h @@ -89,22 +89,14 @@ class BluetoothStatus : public Status case ConnectionState::CONNECTED: LOG_DEBUG("BluetoothStatus CONNECTED"); #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, LOW); -#else - digitalWrite(BLE_LED, HIGH); -#endif + digitalWrite(BLE_LED, LED_STATE_ON); #endif break; case ConnectionState::DISCONNECTED: LOG_DEBUG("BluetoothStatus DISCONNECTED"); #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif break; } diff --git a/src/main.cpp b/src/main.cpp index d9773dfb9..1e0ec041e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -366,11 +366,7 @@ void setup() #ifdef BLE_LED pinMode(BLE_LED, OUTPUT); -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif concurrency::hasBeenSetup = true; @@ -493,7 +489,9 @@ void setup() // The ThinkNodes have their own blink logic // ledPeriodic = new Periodic("Blink", elecrowLedBlinker); #else + ledPeriodic = new Periodic("Blink", ledBlinker); + #endif fsInit(); @@ -834,7 +832,7 @@ void setup() SPI.begin(); #endif #else -// ESP32 + // ESP32 #if defined(HW_SPI1_DEVICE) SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 2a59c0aab..89d74dbd7 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -757,11 +757,7 @@ void NimbleBluetooth::deinit() isDeInit = true; #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif #ifndef NIMBLE_TWO NimBLEDevice::deinit(); diff --git a/variants/esp32/wiphone/variant.h b/variants/esp32/wiphone/variant.h index 619ac622a..5baeb3936 100644 --- a/variants/esp32/wiphone/variant.h +++ b/variants/esp32/wiphone/variant.h @@ -26,7 +26,6 @@ #undef GPS_TX_PIN #define NO_GPS 1 #define HAS_GPS 0 -#define NO_SCREEN #define HAS_SCREEN 0 // Default SPI1 will be mapped to the display diff --git a/variants/esp32s3/esp32-s3-pico/variant.h b/variants/esp32s3/esp32-s3-pico/variant.h index bfcb6059d..65732171a 100644 --- a/variants/esp32s3/esp32-s3-pico/variant.h +++ b/variants/esp32s3/esp32-s3-pico/variant.h @@ -8,7 +8,6 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 0 // 17 -// #define LED_PIN PIN_LED // Board has RGB LED 21 #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h index 6927f1295..c132eba5b 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h @@ -35,8 +35,6 @@ extern "C" { #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED -#define PIN_LED PIN_LED1 - #define LED_STATE_ON 1 // State when LED is lit // XIAO Wio-SX1262 Shield User button diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 7dc1a3ef5..54c7bdfb5 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -51,7 +51,6 @@ extern "C" { #define LED_GREEN PIN_LED1 #define BLE_LED LED_BLUE -#define BLE_LED_INVERTED 1 #define LED_STATE_ON 0 // State when LED is lit // Buttons diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h index 5e7311413..b27cc0297 100644 --- a/variants/rp2040/challenger_2040_lora/pins_arduino.h +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -7,7 +7,7 @@ #define ADC_RESOLUTION (12u) // LEDs -#define PIN_LED (24u) +#define LED_PIN (24u) // Serial #define PIN_SERIAL1_TX (16u) diff --git a/variants/rp2040/challenger_2040_lora/variant.h b/variants/rp2040/challenger_2040_lora/variant.h index 552f90720..f5126cfff 100644 --- a/variants/rp2040/challenger_2040_lora/variant.h +++ b/variants/rp2040/challenger_2040_lora/variant.h @@ -5,8 +5,6 @@ #define EXT_NOTIFY_OUT 0xFFFFFFFF #define BUTTON_PIN 0xFFFFFFFF -#define LED_PIN PIN_LED - #define USE_RF95 // RFM95/SX127x #undef LORA_SCK diff --git a/variants/rp2040/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h index 56214a947..59290bbdb 100644 --- a/variants/rp2040/rak11310/pins_arduino.h +++ b/variants/rp2040/rak11310/pins_arduino.h @@ -23,8 +23,7 @@ static const uint8_t A2 = PIN_A2; static const uint8_t A3 = PIN_A3; // LEDs -#define PIN_LED (23u) -#define PIN_LED1 PIN_LED +#define PIN_LED1 (23u) #define LED_NOTIFICATION (24u) #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index 4d2b9ca3a..cf49ff491 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -10,7 +10,7 @@ #define I2C_SDA1 2 #define I2C_SCL1 3 -#define LED_PIN PIN_LED +#define LED_PIN PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #define BUTTON_PIN 9 diff --git a/variants/rp2040/senselora_rp2040/pins_arduino.h b/variants/rp2040/senselora_rp2040/pins_arduino.h index 61705c8d9..575839cbc 100644 --- a/variants/rp2040/senselora_rp2040/pins_arduino.h +++ b/variants/rp2040/senselora_rp2040/pins_arduino.h @@ -11,8 +11,7 @@ static const uint8_t A2 = PIN_A2; static const uint8_t A3 = PIN_A3; // LEDs -#define PIN_LED (23u) -#define PIN_LED1 PIN_LED +#define PIN_LED1 (23u) #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/senselora_rp2040/variant.h b/variants/rp2040/senselora_rp2040/variant.h index cc90284b7..04e21e073 100644 --- a/variants/rp2040/senselora_rp2040/variant.h +++ b/variants/rp2040/senselora_rp2040/variant.h @@ -5,7 +5,7 @@ #define BUTTON_PIN 2 #define BUTTON_NEED_PULLUP -#define LED_PIN PIN_LED +#define LED_PIN PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #undef BATTERY_PIN From 9d06c1bf34d7b6a3727a319d0f5fa38777ee5297 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 31 Jan 2026 14:55:51 -0600 Subject: [PATCH 074/223] Add StatusMessage module and config overrides (#9351) * Add StatusMessage module and config overrides * Trunk * Don't reboot node simply for a StatusMessage config update --- src/mesh/NodeDB.cpp | 10 ++++++ src/modules/AdminModule.cpp | 18 +++++++++-- src/modules/Modules.cpp | 6 ++++ src/modules/StatusMessageModule.cpp | 41 ++++++++++++++++++++++++ src/modules/StatusMessageModule.h | 35 ++++++++++++++++++++ src/platform/portduino/PortduinoGlue.cpp | 8 +++++ src/platform/portduino/PortduinoGlue.h | 41 ++++++++++++++++-------- 7 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 src/modules/StatusMessageModule.cpp create mode 100644 src/modules/StatusMessageModule.h diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0ba60f1e2..0da4261b9 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1404,6 +1404,15 @@ void NodeDB::loadFromDisk() if (portduino_config.has_configDisplayMode) { config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode; } + if (portduino_config.has_statusMessage) { + moduleConfig.has_statusmessage = true; + strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(), + sizeof(moduleConfig.statusmessage.node_status)); + moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0'; + } + if (portduino_config.enable_UDP) { + config.network.enabled_protocols = true; + } #endif } @@ -1544,6 +1553,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) moduleConfig.has_ambient_lighting = true; moduleConfig.has_audio = true; moduleConfig.has_paxcounter = true; + moduleConfig.has_statusmessage = true; success &= saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 1fda9bf13..61dbc0b92 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -905,10 +905,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { + bool shouldReboot = true; // If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth // Otherwise, disable Bluetooth to prevent the phone from interfering with the config - if (!hasOpenEditTransaction && - !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) { + if (!hasOpenEditTransaction && !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, + meshtastic_ModuleConfig_serial_tag, meshtastic_ModuleConfig_statusmessage_tag)) { disableBluetooth(); } @@ -1000,8 +1001,14 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) moduleConfig.has_paxcounter = true; moduleConfig.paxcounter = c.payload_variant.paxcounter; break; + case meshtastic_ModuleConfig_statusmessage_tag: + LOG_INFO("Set module config: StatusMessage"); + moduleConfig.has_statusmessage = true; + moduleConfig.statusmessage = c.payload_variant.statusmessage; + shouldReboot = false; + break; } - saveChanges(SEGMENT_MODULECONFIG); + saveChanges(SEGMENT_MODULECONFIG, shouldReboot); return true; } @@ -1180,6 +1187,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter; break; + case meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG: + LOG_INFO("Get module config: StatusMessage"); + res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag; + res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage; + break; } // NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior. diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e8da8e983..e8fa4775a 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -90,6 +90,9 @@ #if !MESHTASTIC_EXCLUDE_DROPZONE #include "modules/DropzoneModule.h" #endif +#if !MESHTASTIC_EXCLUDE_STATUS +#include "modules/StatusMessageModule.h" +#endif #if defined(HAS_HARDWARE_WATCHDOG) #include "watchdog/watchdogThread.h" @@ -150,6 +153,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_DROPZONE dropzoneModule = new DropzoneModule(); #endif +#if !MESHTASTIC_EXCLUDE_STATUS + statusMessageModule = new StatusMessageModule(); +#endif #if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE new GenericThreadModule(); #endif diff --git a/src/modules/StatusMessageModule.cpp b/src/modules/StatusMessageModule.cpp new file mode 100644 index 000000000..139a74d8e --- /dev/null +++ b/src/modules/StatusMessageModule.cpp @@ -0,0 +1,41 @@ +#if !MESHTASTIC_EXCLUDE_STATUS + +#include "StatusMessageModule.h" +#include "MeshService.h" +#include "ProtobufModule.h" + +StatusMessageModule *statusMessageModule; + +int32_t StatusMessageModule::runOnce() +{ + if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') { + // create and send message with the status message set + meshtastic_StatusMessage ourStatus = meshtastic_StatusMessage_init_zero; + strncpy(ourStatus.status, moduleConfig.statusmessage.node_status, sizeof(ourStatus.status)); + ourStatus.status[sizeof(ourStatus.status) - 1] = '\0'; // ensure null termination + meshtastic_MeshPacket *p = allocDataPacket(); + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + meshtastic_StatusMessage_fields, &ourStatus); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + p->channel = 0; + service->sendToMesh(p); + } + + return 1000 * 12 * 60 * 60; +} + +ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + meshtastic_StatusMessage incomingMessage; + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields, + &incomingMessage)) { + LOG_INFO("Received a NodeStatus message %s", incomingMessage.status); + } + } + return ProcessMessage::CONTINUE; +} + +#endif \ No newline at end of file diff --git a/src/modules/StatusMessageModule.h b/src/modules/StatusMessageModule.h new file mode 100644 index 000000000..c9ff54018 --- /dev/null +++ b/src/modules/StatusMessageModule.h @@ -0,0 +1,35 @@ +#pragma once +#if !MESHTASTIC_EXCLUDE_STATUS +#include "SinglePortModule.h" +#include "configuration.h" + +class StatusMessageModule : public SinglePortModule, private concurrency::OSThread +{ + + public: + /** Constructor + * name is for debugging output + */ + StatusMessageModule() + : SinglePortModule("statusMessage", meshtastic_PortNum_NODE_STATUS_APP), concurrency::OSThread("StatusMessage") + { + if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') { + this->setInterval(2 * 60 * 1000); + } else { + this->setInterval(1000 * 12 * 60 * 60); + } + // TODO: If we have a string, set the initial delay (15 minutes maybe) + } + + virtual int32_t runOnce() override; + + protected: + /** Called to handle a particular incoming message + */ + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + private: +}; + +extern StatusMessageModule *statusMessageModule; +#endif \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index d0d8ba40f..9579bef45 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -872,6 +872,7 @@ bool loadConfig(const char *configPath) } if (yamlConfig["Config"]) { + portduino_config.has_config_overrides = true; if (yamlConfig["Config"]["DisplayMode"]) { portduino_config.has_configDisplayMode = true; if ((yamlConfig["Config"]["DisplayMode"]).as("") == "TWOCOLOR") { @@ -884,6 +885,13 @@ bool loadConfig(const char *configPath) portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; } } + if (yamlConfig["Config"]["StatusMessage"]) { + portduino_config.has_statusMessage = true; + portduino_config.statusMessage = (yamlConfig["Config"]["StatusMessage"]).as(""); + } + if ((yamlConfig["Config"]["EnableUDP"]).as(false)) { + portduino_config.enable_UDP = true; + } } if (yamlConfig["General"]) { diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index af511be6e..990803e79 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -177,8 +177,12 @@ extern struct portduino_config_struct { int hostMetrics_channel = 0; // config + bool has_config_overrides = false; int configDisplayMode = 0; bool has_configDisplayMode = false; + std::string statusMessage = ""; + bool has_statusMessage = false; + bool enable_UDP = false; // General std::string mac_address = ""; @@ -505,21 +509,30 @@ extern struct portduino_config_struct { } // config - if (has_configDisplayMode) { + if (has_config_overrides) { out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap; - switch (configDisplayMode) { - case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: - out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: - out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: - out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: - out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; - break; + if (has_configDisplayMode) { + + switch (configDisplayMode) { + case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: + out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: + out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; + break; + } + } + if (has_statusMessage) { + out << YAML::Key << "StatusMessage" << YAML::Value << statusMessage; + } + if (enable_UDP) { + out << YAML::Key << "EnableUDP" << YAML::Value << true; } out << YAML::EndMap; // Config From 002214832387ace3ba448cb4e432d6908642bd27 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 1 Feb 2026 19:10:00 -0600 Subject: [PATCH 075/223] Missed in reviews - fixing send bubble (#9505) --- src/graphics/draw/MessageRenderer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 193164439..01fdbb966 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -877,15 +877,15 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Send Message (Right side) display->drawRect(x1 + 2 - bubbleW, y1 - bubbleH, bubbleW, bubbleH); // Top Right Corner - display->drawRect(x1, topY, 2, 1); + display->drawRect(x1 - 1, topY, 2, 1); display->drawRect(x1, topY, 1, 2); // Bottom Right Corner display->drawRect(x1 - 1, bottomY - 2, 2, 1); display->drawRect(x1, bottomY - 3, 1, 2); // Knock the corners off to make a bubble display->setColor(BLACK); - display->drawRect(x1 - bubbleW, topY - 1, 1, 1); - display->drawRect(x1 - bubbleW, bottomY - 1, 1, 1); + display->drawRect(x1 - bubbleW + 2, topY - 1, 1, 1); + display->drawRect(x1 - bubbleW + 2, bottomY - 1, 1, 1); display->setColor(WHITE); } else { // Received Message (Left Side) From f514bc230b86e462c8678d158e942b662bbbb399 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 3 Feb 2026 00:13:49 -0600 Subject: [PATCH 076/223] Prefer EXT_PWR_DETECT pin over chargingVolt to detect power unplugged (#9511) --- src/Power.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Power.cpp b/src/Power.cpp index b211d760e..a7da6f7a9 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -459,6 +459,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } // if it's not HIGH - check the battery #endif + // If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it. + return false; // technically speaking this should work for all(?) NRF52 boards // but needs testing across multiple devices. NRF52 USB would not even work if From 644fa5b54e2231edeba0ecf4d7a45ff16032ad7c Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 3 Feb 2026 06:07:33 -0600 Subject: [PATCH 077/223] Power off control pin on Thinknode m5 during deepsleep and add RTC (#9510) * Power off control pin on Thinknode m5 during deepsleep * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Finish HAS_RTC cleanup * Add RTC for Thinknode M5 * Don't double-init Wire * Specify the RTC chip directly rather than use SensorRtcHelper. Saves a bit of flash, and avoid mis-detection --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/gps/RTC.cpp | 8 ++++++-- src/platform/esp32/main-esp32.cpp | 6 ++++++ src/platform/nrf52/main-nrf52.cpp | 2 +- variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini | 2 ++ variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp | 9 +++++++-- variants/esp32s3/ELECROW-ThinkNode-M5/variant.h | 3 +++ variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M3/variant.h | 1 - variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M6/variant.h | 1 - variants/nrf52840/t-echo-plus/variant.h | 3 --- 11 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 3bca6f6ec..5e31de950 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -72,11 +72,13 @@ RTCSetResult readFromRTC() #elif defined(PCF8563_RTC) || defined(PCF85063_RTC) #if defined(PCF8563_RTC) if (rtc_found.address == PCF8563_RTC) { + SensorPCF8563 rtc; #elif defined(PCF85063_RTC) if (rtc_found.address == PCF85063_RTC) { + SensorPCF85063 rtc; + #endif uint32_t now = millis(); - SensorRtcHelper rtc; #if WIRE_INTERFACES_COUNT == 2 rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); @@ -240,10 +242,12 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd #elif defined(PCF8563_RTC) || defined(PCF85063_RTC) #if defined(PCF8563_RTC) if (rtc_found.address == PCF8563_RTC) { + SensorPCF8563 rtc; #elif defined(PCF85063_RTC) if (rtc_found.address == PCF85063_RTC) { + SensorPCF85063 rtc; + #endif - SensorRtcHelper rtc; #if WIRE_INTERFACES_COUNT == 2 rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 6667acf5c..171911e5e 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -24,6 +24,11 @@ #include #include +// Weak empty variant shutdown prep function. +// May be redefined by variant files. +void variant_shutdown() __attribute__((weak)); +void variant_shutdown() {} + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) { @@ -249,6 +254,7 @@ void cpuDeepSleep(uint32_t msecToWake) #endif // #end ESP32S3_WAKE_TYPE #endif + variant_shutdown(); // We want RTC peripherals to stay on esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 0376a1dad..c705521ad 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -46,7 +46,7 @@ uint16_t getVDDVoltage(); -// Weak empty variant initialization function. +// Weak empty variant shutdown prep function. // May be redefined by variant files. void variant_shutdown() __attribute__((weak)); void variant_shutdown() {} diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini index ee51018d4..92d4bd519 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini @@ -34,3 +34,5 @@ lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip # renovate: datasource=custom.pio depName=PCA9557-arduino packageName=maxpromer/library/PCA9557-arduino maxpromer/PCA9557-arduino@1.0.0 + # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib + lewisxhe/SensorLib@0.3.4 \ No newline at end of file diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp index 4b485a1a3..ac480f83c 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp @@ -1,12 +1,17 @@ #include "variant.h" #include -PCA9557 io(0x18, &Wire); +PCA9557 io(0x18, &Wire1); void earlyInitVariant() { - Wire.begin(48, 47); + Wire1.begin(48, 47); io.pinMode(PCA_PIN_EINK_EN, OUTPUT); io.pinMode(PCA_PIN_POWER_EN, OUTPUT); io.digitalWrite(PCA_PIN_POWER_EN, HIGH); } + +void variant_shutdown() +{ + io.digitalWrite(PCA_PIN_POWER_EN, LOW); +} diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 353741d91..6befac580 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -30,6 +30,9 @@ #define I2C_SCL 1 #define I2C_SDA 2 +// PCF8563 RTC Module +#define PCF8563_RTC 0x51 + // GPS pins #define GPS_SWITH 10 #define HAS_GPS 1 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini index 7a87c2a21..b6956c0f1 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/platformio.ini @@ -24,5 +24,5 @@ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM khoih-prog/nRF52_PWM@1.0.1 - ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib + # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index 50944b6d7..4abec3a0a 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -116,7 +116,6 @@ extern "C" { // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 #ifdef __cplusplus } diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini index 329111ce6..fa09a4463 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/platformio.ini @@ -21,5 +21,5 @@ build_flags = ${nrf52840_base.build_flags} build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M6> lib_deps = ${nrf52840_base.lib_deps} - ; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib + # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index ba6aa14ab..cef26db39 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -121,7 +121,6 @@ static const uint8_t A0 = PIN_A0; // PCF8563 RTC Module #define PCF8563_RTC 0x51 -#define HAS_RTC 1 // SPI #define SPI_INTERFACES_COUNT 1 diff --git a/variants/nrf52840/t-echo-plus/variant.h b/variants/nrf52840/t-echo-plus/variant.h index 4038ce6ef..7ebdf48c0 100644 --- a/variants/nrf52840/t-echo-plus/variant.h +++ b/variants/nrf52840/t-echo-plus/variant.h @@ -132,9 +132,6 @@ static const uint8_t A0 = PIN_A0; #define HAS_DRV2605 1 -// Battery / ADC already defined above -#define HAS_RTC 1 - #define SERIAL_PRINT_PORT 0 #ifdef __cplusplus From 0703e0e6d797621eae32cff355a973ed519bbf16 Mon Sep 17 00:00:00 2001 From: Eric Sesterhenn Date: Tue, 3 Feb 2026 13:22:33 +0100 Subject: [PATCH 078/223] Make sure we always return a value in NodeDB::restorePreferences() (#9516) In case FScom is not defined there is no return statement. This moves the return outside of the ifdef to make sure a defined value is returned. --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0da4261b9..f23d6490b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -2223,8 +2223,8 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, } else if (location == meshtastic_AdminMessage_BackupLocation_SD) { // TODO: After more mainline SD card support } - return success; #endif + return success; } /// Record an error that should be reported via analytics From c8a9cdc148a227a1b40d8e02cdf3e33f207773a9 Mon Sep 17 00:00:00 2001 From: Eric Sesterhenn Date: Tue, 3 Feb 2026 13:22:33 +0100 Subject: [PATCH 079/223] Make sure we always return a value in NodeDB::restorePreferences() (#9516) In case FScom is not defined there is no return statement. This moves the return outside of the ifdef to make sure a defined value is returned. --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0ba60f1e2..1d2fbf8fd 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -2213,8 +2213,8 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, } else if (location == meshtastic_AdminMessage_BackupLocation_SD) { // TODO: After more mainline SD card support } - return success; #endif + return success; } /// Record an error that should be reported via analytics From b008c7a170274c77395be78fb980fa90e2b6d223 Mon Sep 17 00:00:00 2001 From: Jason P Date: Tue, 3 Feb 2026 12:21:51 -0600 Subject: [PATCH 080/223] Fix config.display.use_long_node_name not saving (#9522) --- src/graphics/draw/MenuHandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 6d29e9f7f..9fe018d81 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -1498,6 +1498,7 @@ void menuHandler::nodeNameLengthMenu() config.display.use_long_node_name = option.value; saveUIConfig(); + service->reloadConfig(SEGMENT_CONFIG); LOG_INFO("Setting names to %s", option.value ? "long" : "short"); }); From b7db22055d3a964d188ccbffce4c4ab3a3df6f0e Mon Sep 17 00:00:00 2001 From: Vortetty <33466216+Vortetty@users.noreply.github.com> Date: Tue, 3 Feb 2026 17:02:54 -0800 Subject: [PATCH 081/223] Inkhud battery icon improvements. (#9513) * Inkhud battery icon improvements. Fixes the battery icon draining from the flat side towards the bump, which is backwards from general design language seen on most devices By request of kr0n05_ on discord, adds the ability to mirror the battery icon which fixes that issue in another way, and is also a common design seen on other devices. * Remove option for icon mirroring * Add border + dither to battery to prevent font overlap * Fix trunk format * Code cleanup, courtesy of Xaositek. --- src/graphics/niche/InkHUD/Applet.cpp | 12 +++++++ .../System/BatteryIcon/BatteryIconApplet.cpp | 33 +++++++------------ src/graphics/niche/InkHUD/WindowManager.cpp | 8 ++--- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index ccdd76f97..0616d03cd 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -1,3 +1,5 @@ +#include "graphics/niche/InkHUD/Tile.h" +#include #ifdef MESHTASTIC_INCLUDE_INKHUD #include "./Applet.h" @@ -785,6 +787,16 @@ void InkHUD::Applet::drawHeader(std::string text) drawPixel(x, 0, BLACK); drawPixel(x, headerDivY, BLACK); // Dotted 50% } + + // Dither near battery + if (settings->optionalFeatures.batteryIcon) { + constexpr uint16_t ditherSizePx = 4; + Tile *batteryTile = ((Applet *)inkhud->getSystemApplet("BatteryIcon"))->getTile(); + const uint16_t batteryTileLeft = batteryTile->getLeft(); + const uint16_t batteryTileTop = batteryTile->getTop(); + const uint16_t batteryTileHeight = batteryTile->getHeight(); + hatchRegion(batteryTileLeft - ditherSizePx, batteryTileTop, ditherSizePx, batteryTileHeight, 2, WHITE); + } } // Get the height of the standard applet header diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp index 0cc6f50ed..4fd01c348 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp @@ -48,37 +48,27 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta void InkHUD::BatteryIconApplet::onRender(bool full) { - // Fill entire tile - // - size of icon controlled by size of tile - int16_t l = 0; - int16_t t = 0; - uint16_t w = width(); - int16_t h = height(); - - // Clear the region beneath the tile + // Clear the region beneath the tile, including the border // Most applets are drawing onto an empty frame buffer and don't need to do this // We do need to do this with the battery though, as it is an "overlay" - fillRect(l, t, w, h, WHITE); - - // Vertical centerline - const int16_t m = t + (h / 2); + fillRect(0, 0, width(), height(), WHITE); // ===================== // Draw battery outline // ===================== // Positive terminal "bump" - const int16_t &bumpL = l; - const uint16_t bumpH = h / 2; - const int16_t bumpT = m - (bumpH / 2); constexpr uint16_t bumpW = 2; + const int16_t &bumpL = 1; + const uint16_t bumpH = (height() - 2) / 2; + const int16_t bumpT = (1 + ((height() - 2) / 2)) - (bumpH / 2); fillRect(bumpL, bumpT, bumpW, bumpH, BLACK); // Main body of battery - const int16_t bodyL = bumpL + bumpW; - const int16_t &bodyT = t; - const int16_t &bodyH = h; - const int16_t bodyW = w - bumpW; + const int16_t bodyL = 1 + bumpW; + const int16_t &bodyT = 1; + const int16_t &bodyH = height() - 2; // Handle top/bottom padding + const int16_t bodyW = (width() - 1) - bumpW; // Handle 1px left pad drawRect(bodyL, bodyT, bodyW, bodyH, BLACK); // Erase join between bump and body @@ -89,12 +79,13 @@ void InkHUD::BatteryIconApplet::onRender(bool full) // =================== constexpr int16_t slicePad = 2; - const int16_t sliceL = bodyL + slicePad; + int16_t sliceL = bodyL + slicePad; const int16_t sliceT = bodyT + slicePad; const uint16_t sliceH = bodyH - (slicePad * 2); uint16_t sliceW = bodyW - (slicePad * 2); - sliceW = (sliceW * socRounded) / 100; // Apply percentage + sliceW = (sliceW * socRounded) / 100; // Apply percentage + sliceL += ((bodyW - (slicePad * 2)) - sliceW); // Shift slice to the battery's negative terminal, correcting drain direction hatchRegion(sliceL, sliceT, sliceW, sliceH, 2, BLACK); drawRect(sliceL, sliceT, sliceW, sliceH, BLACK); diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index 9c18fbd48..cec72ce8f 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -510,10 +510,10 @@ void InkHUD::WindowManager::placeSystemTiles() const uint16_t batteryIconWidth = batteryIconHeight * 1.8; inkhud->getSystemApplet("BatteryIcon") ->getTile() - ->setRegion(inkhud->width() - batteryIconWidth, // x - 2, // y - batteryIconWidth, // width - batteryIconHeight); // height + ->setRegion(inkhud->width() - batteryIconWidth - 1, // x + 1, // y + batteryIconWidth + 1, // width + batteryIconHeight + 2); // height // Note: the tiles of placeholder and menu applets are manipulated specially // - menuApplet borrows user tiles From 538a5f0dfc9f6cd587b076f6c80a801b51a5f064 Mon Sep 17 00:00:00 2001 From: Mattatat25 <108779801+mattatat25@users.noreply.github.com> Date: Tue, 3 Feb 2026 20:45:02 -0600 Subject: [PATCH 082/223] Add reply bot module with DM-only responses and rate limiting (#9456) * Implement Meshtastic reply bot module with ping and status features Adds a reply bot module that listens for /ping, /hello, and /test commands received via direct messages or broadcasts on the primary channel. The module always replies via direct message to the sender only, reporting hop count, RSSI, and SNR. Per-sender cooldowns are enforced to reduce network spam, and the module can be excluded at build time via a compile flag. Updates include the new module source files and required build configuration changes. * Update ReplyBotModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/ReplyBotModule.h Match the existing MESHTASTIC_EXCLUDE_* guard pattern so the module is excluded by default. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/ReplyBotModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Tidying up --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors --- platformio.ini | 1 + src/modules/ReplyBotModule.cpp | 183 +++++++++++++++++++++++++++++++++ src/modules/ReplyBotModule.h | 18 ++++ 3 files changed, 202 insertions(+) create mode 100644 src/modules/ReplyBotModule.cpp create mode 100644 src/modules/ReplyBotModule.h diff --git a/platformio.ini b/platformio.ini index 77e9cf214..291915d3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,6 +50,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_APRS=1 -DRADIOLIB_EXCLUDE_LORAWAN=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 + -DMESHTASTIC_EXCLUDE_REPLYBOT=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 -DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1 -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware diff --git a/src/modules/ReplyBotModule.cpp b/src/modules/ReplyBotModule.cpp new file mode 100644 index 000000000..90223cacb --- /dev/null +++ b/src/modules/ReplyBotModule.cpp @@ -0,0 +1,183 @@ +#if !MESHTASTIC_EXCLUDE_REPLYBOT +/* + * ReplyBotModule.cpp + * + * This module implements a simple reply bot for the Meshtastic firmware. It listens for + * specific text commands ("/ping", "/hello" and "/test") delivered either via a direct + * message (DM) or a broadcast on the primary channel. When a supported command is + * received the bot responds with a short status message that includes the hop count + * (minimum number of relays), RSSI and SNR of the received packet. To avoid spamming + * the network it enforces a per‑sender cooldown between responses. By default the + * module is enabled; define MESHTASTIC_EXCLUDE_REPLYBOT at build time to exclude it + * entirely. See the official firmware documentation for guidance on adding modules. + */ + +#include "ReplyBotModule.h" +#include "Channels.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "configuration.h" +#include "mesh/MeshTypes.h" + +#include +#include +#include + +// +// Rate limiting data structures +// +// Each sender is tracked in a small ring buffer. When a message arrives from a +// sender we check the last time we responded to them. If the difference is +// less than the configured cooldown (different values for DM vs broadcast) +// the message is ignored; otherwise we update the last response time and +// proceed with replying. + +struct ReplyBotCooldownEntry { + uint32_t from = 0; + uint32_t lastMs = 0; +}; + +static constexpr uint8_t REPLYBOT_COOLDOWN_SLOTS = 8; // ring buffer size +static constexpr uint32_t REPLYBOT_DM_COOLDOWN_MS = 15 * 1000; // 15 seconds for DMs +static constexpr uint32_t REPLYBOT_LF_COOLDOWN_MS = 60 * 1000; // 60 seconds for LongFast broadcasts + +static ReplyBotCooldownEntry replybotCooldown[REPLYBOT_COOLDOWN_SLOTS]; +static uint8_t replybotCooldownIdx = 0; + +// Return true if a reply should be rate‑limited for this sender, updating the +// entry table as needed. +static bool replybotRateLimited(uint32_t from, uint32_t cooldownMs) +{ + const uint32_t now = millis(); + for (auto &e : replybotCooldown) { + if (e.from == from) { + // Found existing entry; check if cooldown expired + if ((uint32_t)(now - e.lastMs) < cooldownMs) { + return true; + } + e.lastMs = now; + return false; + } + } + // No entry found – insert new sender into the ring + replybotCooldown[replybotCooldownIdx].from = from; + replybotCooldown[replybotCooldownIdx].lastMs = now; + replybotCooldownIdx = (replybotCooldownIdx + 1) % REPLYBOT_COOLDOWN_SLOTS; + return false; +} + +// Constructor – registers a single text port and marks the module promiscuous +// so that broadcast messages on the primary channel are visible. +ReplyBotModule::ReplyBotModule() : SinglePortModule("replybot", meshtastic_PortNum_TEXT_MESSAGE_APP) +{ + isPromiscuous = true; +} + +void ReplyBotModule::setup() +{ + // In future we may add a protobuf configuration; for now the module is + // always enabled when compiled in. +} + +// Determine whether we want to process this packet. We only care about +// plain text messages addressed to our port. +bool ReplyBotModule::wantPacket(const meshtastic_MeshPacket *p) +{ + return (p && p->decoded.portnum == ourPortNum); +} + +ProcessMessage ReplyBotModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + // Accept only direct messages to us or broadcasts on the Primary channel + // (regardless of modem preset: LongFast, MediumFast, etc). + + const uint32_t ourNode = nodeDB->getNodeNum(); + const bool isDM = (mp.to == ourNode); + const bool isPrimaryChannel = (mp.channel == channels.getPrimaryIndex()) && isBroadcast(mp.to); + if (!isDM && !isPrimaryChannel) { + return ProcessMessage::CONTINUE; + } + + // Ignore empty payloads + if (mp.decoded.payload.size == 0) { + return ProcessMessage::CONTINUE; + } + + // Copy payload into a null‑terminated buffer + char buf[260]; + memset(buf, 0, sizeof(buf)); + size_t n = mp.decoded.payload.size; + if (n > sizeof(buf) - 1) + n = sizeof(buf) - 1; + memcpy(buf, mp.decoded.payload.bytes, n); + + // React only to supported slash commands + if (!isCommand(buf)) { + return ProcessMessage::CONTINUE; + } + + // Apply rate limiting per sender depending on DM/broadcast + const uint32_t cooldownMs = isDM ? REPLYBOT_DM_COOLDOWN_MS : REPLYBOT_LF_COOLDOWN_MS; + if (replybotRateLimited(mp.from, cooldownMs)) { + return ProcessMessage::CONTINUE; + } + + // Compute hop count indicator – if the relay_node is non‑zero we know + // there was at least one relay. Some firmware builds support a hop_start + // field which could be used for more accurate counts, but here we use + // the available relay_node flag only. + // int hopsAway = mp.hop_start - mp.hop_limit; + int hopsAway = getHopsAway(mp); + + // Normalize RSSI: if positive adjust down by 200 to align with typical values + int rssi = mp.rx_rssi; + if (rssi > 0) { + rssi -= 200; + } + float snr = mp.rx_snr; + + // Build the reply message and send it back via DM + char reply[96]; + snprintf(reply, sizeof(reply), "🎙️ Mic Check : %d Hops away | RSSI %d | SNR %.1f", hopsAway, rssi, snr); + sendDm(mp, reply); + return ProcessMessage::CONTINUE; +} + +// Check if the message starts with one of the supported commands. Leading +// whitespace is skipped and commands must be followed by end‑of‑string or +// whitespace. +bool ReplyBotModule::isCommand(const char *msg) const +{ + if (!msg) + return false; + while (*msg == ' ' || *msg == '\t') + msg++; + auto isEndOrSpace = [](char c) { return c == '\0' || std::isspace(static_cast(c)); }; + if (strncmp(msg, "/ping", 5) == 0 && isEndOrSpace(msg[5])) + return true; + if (strncmp(msg, "/hello", 6) == 0 && isEndOrSpace(msg[6])) + return true; + if (strncmp(msg, "/test", 5) == 0 && isEndOrSpace(msg[5])) + return true; + return false; +} + +// Send a direct message back to the originating node. +void ReplyBotModule::sendDm(const meshtastic_MeshPacket &rx, const char *text) +{ + if (!text) + return; + meshtastic_MeshPacket *p = allocDataPacket(); + p->to = rx.from; + p->channel = rx.channel; + p->want_ack = false; + p->decoded.want_response = false; + size_t len = strlen(text); + if (len > sizeof(p->decoded.payload.bytes)) { + len = sizeof(p->decoded.payload.bytes); + } + p->decoded.payload.size = len; + memcpy(p->decoded.payload.bytes, text, len); + service->sendToMesh(p); +} +#endif // MESHTASTIC_EXCLUDE_REPLYBOT \ No newline at end of file diff --git a/src/modules/ReplyBotModule.h b/src/modules/ReplyBotModule.h new file mode 100644 index 000000000..7413667ca --- /dev/null +++ b/src/modules/ReplyBotModule.h @@ -0,0 +1,18 @@ +#pragma once +#if !MESHTASTIC_EXCLUDE_REPLYBOT +#include "SinglePortModule.h" +#include "mesh/generated/meshtastic/mesh.pb.h" + +class ReplyBotModule : public SinglePortModule +{ + public: + ReplyBotModule(); + void setup() override; + bool wantPacket(const meshtastic_MeshPacket *p) override; + ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + protected: + bool isCommand(const char *msg) const; + void sendDm(const meshtastic_MeshPacket &rx, const char *text); +}; +#endif // MESHTASTIC_EXCLUDE_REPLYBOT \ No newline at end of file From bfc3eebd54d4be183607ac4e0f957f27ad95e74f Mon Sep 17 00:00:00 2001 From: Jason P Date: Wed, 4 Feb 2026 08:56:50 -0600 Subject: [PATCH 083/223] HotFix for ReplyBot - Modules.cpp included and moved configuration.h (#9532) --- src/modules/Modules.cpp | 7 ++++++- src/modules/ReplyBotModule.cpp | 4 ++-- src/modules/ReplyBotModule.h | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e8fa4775a..a73e59ac9 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -4,6 +4,9 @@ #include "modules/StatusLEDModule.h" #include "modules/SystemCommandsModule.h" #endif +#if !MESHTASTIC_EXCLUDE_REPLYBOT +#include "ReplyBotModule.h" +#endif #if !MESHTASTIC_EXCLUDE_PKI #include "KeyVerificationModule.h" #endif @@ -112,7 +115,9 @@ void setupModules() #if defined(LED_CHARGE) || defined(LED_PAIRING) statusLEDModule = new StatusLEDModule(); #endif - +#if !MESHTASTIC_EXCLUDE_REPLYBOT + new ReplyBotModule(); +#endif #if !MESHTASTIC_EXCLUDE_ADMIN adminModule = new AdminModule(); #endif diff --git a/src/modules/ReplyBotModule.cpp b/src/modules/ReplyBotModule.cpp index 90223cacb..544b3a1d9 100644 --- a/src/modules/ReplyBotModule.cpp +++ b/src/modules/ReplyBotModule.cpp @@ -1,3 +1,4 @@ +#include "configuration.h" #if !MESHTASTIC_EXCLUDE_REPLYBOT /* * ReplyBotModule.cpp @@ -12,11 +13,10 @@ * entirely. See the official firmware documentation for guidance on adding modules. */ -#include "ReplyBotModule.h" #include "Channels.h" #include "MeshService.h" #include "NodeDB.h" -#include "configuration.h" +#include "ReplyBotModule.h" #include "mesh/MeshTypes.h" #include diff --git a/src/modules/ReplyBotModule.h b/src/modules/ReplyBotModule.h index 7413667ca..a5a8f6bb4 100644 --- a/src/modules/ReplyBotModule.h +++ b/src/modules/ReplyBotModule.h @@ -1,4 +1,5 @@ #pragma once +#include "configuration.h" #if !MESHTASTIC_EXCLUDE_REPLYBOT #include "SinglePortModule.h" #include "mesh/generated/meshtastic/mesh.pb.h" From be5f0a9ade316b7bda9feeb668e6b80613f88e72 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 4 Feb 2026 11:15:38 -0600 Subject: [PATCH 084/223] Implement UDP multicast handler start/stop to ensure proper lifecycle (#9524) * Implement UDP multicast handler start/stop to ensure proper lifecycle * Add close method to AsyncUDP and improve UDP multicast handler lifecycle management * Guard portduino --- src/mesh/udp/UdpMulticastHandler.h | 25 +++++++++++++++++++++++-- src/mesh/wifi/WiFiAPClient.cpp | 10 ++++++++++ src/platform/nrf52/AsyncUDP.cpp | 7 +++++++ src/platform/nrf52/AsyncUDP.h | 1 + 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index 2df8686a3..d79b63ceb 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -22,10 +22,14 @@ class UdpMulticastHandler final { public: - UdpMulticastHandler() { udpIpAddress = IPAddress(224, 0, 0, 69); } + UdpMulticastHandler() : isRunning(false) { udpIpAddress = IPAddress(224, 0, 0, 69); } void start() { + if (isRunning) { + LOG_DEBUG("UDP multicast already running"); + return; + } if (udp.listenMulticast(udpIpAddress, UDP_MULTICAST_DEFAUL_PORT, 64)) { #if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) LOG_DEBUG("UDP Listening on IP: %u.%u.%u.%u:%u", udpIpAddress[0], udpIpAddress[1], udpIpAddress[2], udpIpAddress[3], @@ -34,13 +38,29 @@ class UdpMulticastHandler final LOG_DEBUG("UDP Listening on IP: %s", WiFi.localIP().toString().c_str()); #endif udp.onPacket([this](AsyncUDPPacket packet) { onReceive(packet); }); + isRunning = true; } else { LOG_DEBUG("Failed to listen on UDP"); } } + void stop() + { + if (!isRunning) { + return; + } + LOG_DEBUG("Stopping UDP multicast"); +#if defined(ARCH_ESP32) || defined(ARCH_NRF52) + udp.close(); +#endif + isRunning = false; + } + void onReceive(AsyncUDPPacket packet) { + if (!isRunning) { + return; + } size_t packetLength = packet.length(); #if defined(ARCH_NRF52) IPAddress ip = packet.remoteIP(); @@ -67,7 +87,7 @@ class UdpMulticastHandler final bool onSend(const meshtastic_MeshPacket *mp) { - if (!mp || !udp) { + if (!isRunning || !mp || !udp) { return false; } #if defined(ARCH_NRF52) @@ -92,5 +112,6 @@ class UdpMulticastHandler final private: IPAddress udpIpAddress; AsyncUDP udp; + bool isRunning; }; #endif // HAS_UDP_MULTICAST \ No newline at end of file diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index a95dfa58f..5d05c7fc6 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -391,6 +391,11 @@ static void WiFiEvent(WiFiEvent_t event) LOG_INFO("Disconnected from WiFi access point"); #ifdef WIFI_LED digitalWrite(WIFI_LED, LOW); +#endif +#if HAS_UDP_MULTICAST + if (udpHandler) { + udpHandler->stop(); + } #endif if (!isReconnecting) { WiFi.disconnect(false, true); @@ -417,6 +422,11 @@ static void WiFiEvent(WiFiEvent_t event) break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: LOG_INFO("Lost IP address and IP address is reset to 0"); +#if HAS_UDP_MULTICAST + if (udpHandler) { + udpHandler->stop(); + } +#endif if (!isReconnecting) { WiFi.disconnect(false, true); syslog.disable(); diff --git a/src/platform/nrf52/AsyncUDP.cpp b/src/platform/nrf52/AsyncUDP.cpp index 836fb1307..d119ffddb 100644 --- a/src/platform/nrf52/AsyncUDP.cpp +++ b/src/platform/nrf52/AsyncUDP.cpp @@ -36,6 +36,13 @@ bool AsyncUDP::writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t p return udp.endPacket(); } +void AsyncUDP::close() +{ + udp.stop(); + localPort = 0; + _onPacket = nullptr; +} + // AsyncUDPPacket AsyncUDPPacket::AsyncUDPPacket(EthernetUDP &source) : _udp(source), _remoteIP(source.remoteIP()), _remotePort(source.remotePort()) { diff --git a/src/platform/nrf52/AsyncUDP.h b/src/platform/nrf52/AsyncUDP.h index e2b406ba9..718277309 100644 --- a/src/platform/nrf52/AsyncUDP.h +++ b/src/platform/nrf52/AsyncUDP.h @@ -22,6 +22,7 @@ class AsyncUDP : public Print, private concurrency::OSThread bool listenMulticast(IPAddress multicastIP, uint16_t port, uint8_t ttl = 64); bool writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t port); + void close(); size_t write(uint8_t b) override; size_t write(const uint8_t *data, size_t len) override; From 89df5ef6698552177ca2fb14c933d93d069a0c43 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 4 Feb 2026 20:15:49 +0300 Subject: [PATCH 085/223] Undefine LED_BUILTIN (#9531) Keep variant in sync with https://github.com/meshtastic/firmware/commit/df40085 Co-authored-by: Ben Meadors --- variants/esp32/tlora_v2_1_16_tcxo/platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini index a6b9d2254..235ac7007 100644 --- a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini +++ b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini @@ -7,4 +7,5 @@ build_flags = -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -D LORA_TCXO_GPIO=33 -upload_speed = 115200 \ No newline at end of file + -ULED_BUILTIN +upload_speed = 115200 From ac611c4b6262ae54336317c9e4167827ba6edd78 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 4 Feb 2026 14:47:44 -0600 Subject: [PATCH 086/223] Add agc reset attempt (#8163) * Add agc reset attempt * Add radioLibInterface include * Trunk * AGC reset don't crash, don't naively call * Update src/main.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Use Throttle function --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main.cpp | 9 +++++++++ src/main.h | 1 + src/mesh/RadioLibInterface.cpp | 2 ++ 3 files changed, 12 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 1e0ec041e..08b6707fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "PowerMon.h" +#include "RadioLibInterface.h" #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" @@ -193,6 +194,8 @@ bool kb_found = false; // global bool to record that on-screen keyboard (OSK) is present bool osk_found = false; +unsigned long last_listen = 0; + // The I2C address of the RTC Module (if found) ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE; // The I2C address of the Accelerometer (if found) @@ -1166,6 +1169,12 @@ void loop() #endif power->powerCommandsCheck(); + if (RadioLibInterface::instance != nullptr && !Throttle::isWithinTimespanMs(last_listen, 1000 * 60) && + !(RadioLibInterface::instance->isSending() || RadioLibInterface::instance->isActivelyReceiving())) { + RadioLibInterface::instance->startReceive(); + LOG_DEBUG("attempting AGC reset"); + } + #ifdef DEBUG_STACK static uint32_t lastPrint = 0; if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) { diff --git a/src/main.h b/src/main.h index 91e27951f..619eb184e 100644 --- a/src/main.h +++ b/src/main.h @@ -33,6 +33,7 @@ extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; extern bool kb_found; extern bool osk_found; +extern unsigned long last_listen; extern ScanI2C::DeviceAddress rtc_found; extern ScanI2C::DeviceAddress accelerometer_found; extern ScanI2C::FoundDevice rgb_found; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 80e51b8bc..6716109b6 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -514,6 +514,8 @@ void RadioLibInterface::handleReceiveInterrupt() void RadioLibInterface::startReceive() { + // Note the updated timestamp, to avoid unneeded AGC resets + last_listen = millis(); isReceiving = true; powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); } From 94b71499582778d2c664371b3938fac3ae536913 Mon Sep 17 00:00:00 2001 From: Eric Sesterhenn Date: Thu, 5 Feb 2026 00:11:44 +0100 Subject: [PATCH 087/223] Remove unused hmx variable (#9529) The variable is not used at all in the function, remove it to silence the compiler warning. Co-authored-by: Ben Meadors --- src/graphics/SharedUIDisplay.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 8f06fcf9f..ab21b2a03 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -221,7 +221,6 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti if (rtc_sec > 0) { // === Build Time String === - long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY; int hour, minute, second; graphics::decomposeTime(rtc_sec, hour, minute, second); snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute); From b23874444500576aa58a1cdd048b6892fbb46dcb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:12:01 -0600 Subject: [PATCH 088/223] Update Adafruit MPU6050 to v2.2.7 (#9525) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 77e9cf214..5e70b2b68 100644 --- a/platformio.ini +++ b/platformio.ini @@ -144,7 +144,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219 adafruit/Adafruit INA219@1.2.3 # renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050 - adafruit/Adafruit MPU6050@2.2.6 + adafruit/Adafruit MPU6050@2.2.7 # renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH adafruit/Adafruit LIS3DH@1.3.0 # renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0 From 74ea6206d9519ee76bc8a9cc0b3e42b9c4680ab9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:13:44 -0600 Subject: [PATCH 089/223] Update NeoPixel to v1.15.3 (#9530) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/betafpv_2400_tx_micro/platformio.ini | 2 +- variants/esp32c3/heltec_hru_3601/platformio.ini | 2 +- variants/esp32c6/m5stack_unitc6l/platformio.ini | 2 +- variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini | 2 +- variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini | 2 +- variants/esp32s3/esp32-s3-pico/platformio.ini | 2 +- variants/esp32s3/heltec_sensor_hub/platformio.ini | 2 +- variants/esp32s3/unphone/platformio.ini | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/variants/esp32/betafpv_2400_tx_micro/platformio.ini b/variants/esp32/betafpv_2400_tx_micro/platformio.ini index 77a1f7043..f3d488f43 100644 --- a/variants/esp32/betafpv_2400_tx_micro/platformio.ini +++ b/variants/esp32/betafpv_2400_tx_micro/platformio.ini @@ -16,4 +16,4 @@ upload_speed = 460800 lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.2 + adafruit/Adafruit NeoPixel@1.15.3 diff --git a/variants/esp32c3/heltec_hru_3601/platformio.ini b/variants/esp32c3/heltec_hru_3601/platformio.ini index 8200b6e87..7cf60fc08 100644 --- a/variants/esp32c3/heltec_hru_3601/platformio.ini +++ b/variants/esp32c3/heltec_hru_3601/platformio.ini @@ -7,4 +7,4 @@ build_flags = -I variants/esp32c3/heltec_hru_3601 lib_deps = ${esp32c3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.2 + adafruit/Adafruit NeoPixel@1.15.3 diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index ed26598d2..b14fc83f2 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -24,7 +24,7 @@ build_unflags = lib_deps = ${esp32c6_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.2 + adafruit/Adafruit NeoPixel@1.15.3 # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino h2zero/NimBLE-Arduino@2.3.7 build_flags = diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index f44f26006..e5fae20d5 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -13,7 +13,7 @@ lib_deps = # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.2 + adafruit/Adafruit NeoPixel@1.15.3 build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 diff --git a/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini index 4f759d1e6..529d9182e 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini @@ -11,7 +11,7 @@ upload_speed = 921600 lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.2 + adafruit/Adafruit NeoPixel@1.15.3 build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 diff --git a/variants/esp32s3/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini index db0c038e6..f75ab4f24 100644 --- a/variants/esp32s3/esp32-s3-pico/platformio.ini +++ b/variants/esp32s3/esp32-s3-pico/platformio.ini @@ -25,4 +25,4 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 zinggjm/GxEPD2@1.6.6 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.2 + adafruit/Adafruit NeoPixel@1.15.3 diff --git a/variants/esp32s3/heltec_sensor_hub/platformio.ini b/variants/esp32s3/heltec_sensor_hub/platformio.ini index 3f93c7ad2..3d8fe932d 100644 --- a/variants/esp32s3/heltec_sensor_hub/platformio.ini +++ b/variants/esp32s3/heltec_sensor_hub/platformio.ini @@ -11,4 +11,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.2 + adafruit/Adafruit NeoPixel@1.15.3 diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index 924dfa74f..045454e8e 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${esp32s3_base.lib_deps} # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.2 + adafruit/Adafruit NeoPixel@1.15.3 [env:unphone-tft] board_level = extra From 5df5ab27901b442900a674c434add873f0212b52 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:19:11 -0600 Subject: [PATCH 090/223] Update Adafruit MPU6050 to v2.2.8 (#9534) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 5e70b2b68..a29cdbb2d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -144,7 +144,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219 adafruit/Adafruit INA219@1.2.3 # renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050 - adafruit/Adafruit MPU6050@2.2.7 + adafruit/Adafruit MPU6050@2.2.8 # renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH adafruit/Adafruit LIS3DH@1.3.0 # renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0 From a324c4af10561af49cba23aa5fa2459b83fa38ed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:30:12 -0600 Subject: [PATCH 091/223] Update meshtastic-esp8266-oled-ssd1306 digest to 21e484f (#9533) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ben Meadors --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index a29cdbb2d..777b02b7b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -66,7 +66,7 @@ monitor_speed = 115200 monitor_filters = direct lib_deps = # renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master - https://github.com/meshtastic/esp8266-oled-ssd1306/archive/b34c6817c25d6faabb3a8a162b5d14fb75395433.zip + https://github.com/meshtastic/esp8266-oled-ssd1306/archive/21e484f409cde18d44012caef84c244eb5ca28f3.zip # renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip # renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master From 2361776992adff08b623e2f3b110ffe98978342e Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 5 Feb 2026 05:41:00 -0600 Subject: [PATCH 092/223] Rename LED_PIN to LED_POWER, move handling out of main to dedicated module (#9512) * Rename LED_PIN to LED_POWER, move handling out of main to dedicated module * Misc * Remove errant endif --- src/Led.cpp | 66 --------------- src/Led.h | 7 -- src/PowerFSM.cpp | 8 +- src/main.cpp | 63 ++------------- src/mesh/http/ContentHandler.cpp | 43 ---------- src/mesh/http/ContentHandler.h | 1 - src/modules/AdminModule.cpp | 6 -- src/modules/Modules.cpp | 4 +- src/modules/PowerStressModule.cpp | 7 +- src/modules/StatusLEDModule.cpp | 80 ++++++++++++++++++- src/modules/StatusLEDModule.h | 13 ++- src/platform/nrf52/architecture.h | 2 +- src/sleep.cpp | 5 +- .../esp32/betafpv_2400_tx_micro/variant.h | 2 +- variants/esp32/betafpv_900_tx_nano/variant.h | 2 +- variants/esp32/chatter2/variant.h | 2 - .../diy/9m2ibr_aprs_lora_tracker/variant.h | 4 +- variants/esp32/diy/hydra/variant.h | 2 +- variants/esp32/diy/v1/variant.h | 2 +- variants/esp32/hackerboxes_esp32_io/variant.h | 2 +- variants/esp32/heltec_v1/variant.h | 2 +- variants/esp32/heltec_v2.1/variant.h | 2 +- variants/esp32/heltec_v2/variant.h | 2 +- .../esp32/heltec_wireless_bridge/variant.h | 2 +- variants/esp32/heltec_wsl_v2.1/variant.h | 2 +- variants/esp32/m5stack_coreink/variant.h | 2 +- .../radiomaster_900_bandit_nano/variant.h | 2 +- variants/esp32/rak11200/variant.h | 2 +- variants/esp32/tbeam/variant.h | 4 +- variants/esp32/tlora_v1/variant.h | 2 +- variants/esp32/tlora_v1_3/variant.h | 2 +- variants/esp32/tlora_v2/variant.h | 2 +- variants/esp32/tlora_v2_1_16/platformio.ini | 2 +- variants/esp32/tlora_v2_1_16/variant.h | 6 +- variants/esp32/tlora_v2_1_18/variant.h | 2 +- variants/esp32/trackerd/variant.h | 2 +- variants/esp32c3/ai-c3/variant.h | 2 +- .../hackerboxes_esp32c3_oled/variant.h | 2 +- variants/esp32c3/heltec_esp32c3/variant.h | 2 +- variants/esp32c6/tlora_c6/variant.h | 2 +- variants/esp32s2/nugget_s2_lora/variant.h | 2 +- variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h | 2 +- variants/esp32s3/CDEBYTE_EoRa-S3/variant.h | 2 +- variants/esp32s3/EBYTE_ESP32-S3/variant.h | 2 +- .../esp32s3/ELECROW-ThinkNode-M2/variant.h | 2 +- .../esp32s3/ELECROW-ThinkNode-M5/variant.cpp | 6 ++ .../esp32s3/ELECROW-ThinkNode-M5/variant.h | 6 +- variants/esp32s3/bpi_picow_esp32_s3/variant.h | 2 +- .../crowpanel-esp32s3-5-epaper/variant.h | 2 +- .../esp32s3/diy/my_esp32s3_diy_eink/variant.h | 2 +- .../esp32s3/diy/my_esp32s3_diy_oled/variant.h | 2 +- variants/esp32s3/dreamcatcher/variant.h | 2 +- .../heltec_capsule_sensor_v3/variant.h | 4 +- variants/esp32s3/heltec_v3/variant.h | 2 +- variants/esp32s3/heltec_v4/platformio.ini | 2 +- .../heltec_vision_master_e213/variant.h | 2 +- .../heltec_vision_master_e290/variant.h | 2 +- .../esp32s3/heltec_wireless_paper/variant.h | 2 +- .../heltec_wireless_paper_v1/variant.h | 2 +- .../esp32s3/heltec_wireless_tracker/variant.h | 2 +- .../heltec_wireless_tracker_V1_0/variant.h | 2 +- .../heltec_wireless_tracker_v2/variant.h | 2 +- variants/esp32s3/heltec_wsl_v3/variant.h | 2 +- variants/esp32s3/mesh-tab/variant.h | 2 +- variants/esp32s3/nibble_esp32/variant.h | 2 +- variants/esp32s3/nugget_s3_lora/variant.h | 2 +- variants/esp32s3/rak3312/variant.h | 2 +- variants/esp32s3/rak_wismesh_tap_v2/variant.h | 2 +- variants/esp32s3/seeed_xiao_s3/variant.h | 2 +- variants/esp32s3/t-beam-1w/variant.h | 2 +- variants/esp32s3/t-eth-elite/variant.h | 2 +- variants/esp32s3/tlora_t3s3_epaper/variant.h | 2 +- variants/esp32s3/tlora_t3s3_v1/variant.h | 2 +- .../esp32s3/tracksenger/internal/variant.h | 2 +- variants/esp32s3/tracksenger/lcd/variant.h | 2 +- variants/esp32s3/tracksenger/oled/variant.h | 2 +- variants/esp32s3/unphone/variant.h | 2 +- .../nrf52840/ELECROW-ThinkNode-M3/variant.h | 1 - .../nrf52840/ELECROW-ThinkNode-M6/variant.cpp | 3 - .../nrf52840/ELECROW-ThinkNode-M6/variant.h | 2 +- variants/nrf52840/ME25LS01-4Y10TD/variant.cpp | 3 - variants/nrf52840/ME25LS01-4Y10TD/variant.h | 2 +- .../ME25LS01-4Y10TD_e-ink/variant.cpp | 3 - .../nrf52840/ME25LS01-4Y10TD_e-ink/variant.h | 2 +- variants/nrf52840/MS24SF1/variant.h | 2 +- .../nrf52840/seeed_solar_node/variant.cpp | 1 - .../seeed_xiao_nrf52840_kit/variant.h | 2 +- variants/nrf52840/tracker-t1000-e/variant.cpp | 4 - variants/nrf52840/tracker-t1000-e/variant.h | 2 +- variants/nrf52840/wio-t1000-s/variant.cpp | 4 - variants/nrf52840/wio-t1000-s/variant.h | 2 +- .../challenger_2040_lora/pins_arduino.h | 2 +- variants/rp2040/ec_catsniffer/variant.h | 2 +- .../rp2040/feather_rp2040_rfm95/variant.h | 2 +- variants/rp2040/nibble_rp2040/variant.h | 2 +- variants/rp2040/rak11310/variant.h | 2 +- variants/rp2040/rp2040-lora/variant.h | 2 +- variants/rp2040/rpipico-slowclock/variant.h | 2 +- variants/rp2040/rpipico/variant.h | 2 +- variants/rp2040/rpipicow/variant.h | 2 +- variants/rp2040/senselora_rp2040/variant.h | 2 +- variants/rp2350/rpipico2/variant.h | 2 +- variants/stm32/CDEBYTE_E77-MBL/variant.h | 4 +- variants/stm32/milesight_gs301/variant.h | 2 +- variants/stm32/rak3172/variant.h | 2 +- variants/stm32/russell/variant.h | 2 +- variants/stm32/wio-e5/variant.h | 2 +- 107 files changed, 207 insertions(+), 311 deletions(-) delete mode 100644 src/Led.cpp delete mode 100644 src/Led.h diff --git a/src/Led.cpp b/src/Led.cpp deleted file mode 100644 index 6406cd2f7..000000000 --- a/src/Led.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "Led.h" -#include "PowerMon.h" -#include "main.h" -#include "power.h" - -GpioVirtPin ledForceOn, ledBlink; - -#if defined(LED_PIN) -// Most boards have a GPIO for LED control -static GpioHwPin ledRawHwPin(LED_PIN); -#else -static GpioVirtPin ledRawHwPin; // Dummy pin for no hardware -#endif - -#if LED_STATE_ON == 0 -static GpioVirtPin ledHwPin; -static GpioNotTransformer ledInverter(&ledHwPin, &ledRawHwPin); -#else -static GpioPin &ledHwPin = ledRawHwPin; -#endif - -#if defined(HAS_PMU) -/** - * A GPIO controlled by the PMU - */ -class GpioPmuPin : public GpioPin -{ - public: - void set(bool value) - { - if (pmu_found && PMU) { - // blink the axp led - PMU->setChargingLedMode(value ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); - } - } -} ledPmuHwPin; - -// In some cases we need to drive a PMU LED and a normal LED -static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin); -#else -static GpioPin &ledFinalPin = ledHwPin; -#endif - -#ifdef USE_POWERMON -/** - * We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff. - */ -class MonitoredLedPin : public GpioPin -{ - public: - void set(bool value) - { - if (powerMon) { - if (value) - powerMon->setState(meshtastic_PowerMon_State_LED_On); - else - powerMon->clearState(meshtastic_PowerMon_State_LED_On); - } - ledFinalPin.set(value); - } -} monitoredLedPin; -#else -static GpioPin &monitoredLedPin = ledFinalPin; -#endif - -static GpioBinaryTransformer ledForcer(&ledForceOn, &ledBlink, &monitoredLedPin, GpioBinaryTransformer::Or); \ No newline at end of file diff --git a/src/Led.h b/src/Led.h deleted file mode 100644 index 68833e041..000000000 --- a/src/Led.h +++ /dev/null @@ -1,7 +0,0 @@ -#include "GpioLogic.h" -#include "configuration.h" - -/** - * ledForceOn and ledForceOff both override the normal ledBlinker behavior (which is controlled by main) - */ -extern GpioVirtPin ledForceOn, ledBlink; \ No newline at end of file diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 9f8097b84..2153dbfd5 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -9,13 +9,13 @@ */ #include "PowerFSM.h" #include "Default.h" -#include "Led.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerMon.h" #include "configuration.h" #include "graphics/Screen.h" #include "main.h" +#include "modules/StatusLEDModule.h" #include "sleep.h" #include "target_specific.h" @@ -103,7 +103,7 @@ static void lsIdle() uint32_t sleepTime = SLEEP_TIME; powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); - ledBlink.set(false); // Never leave led on while in light sleep + statusLEDModule->setPowerLED(false); esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); @@ -111,7 +111,7 @@ static void lsIdle() case ESP_SLEEP_WAKEUP_TIMER: // Normal case: timer expired, we should just go back to sleep ASAP - ledBlink.set(true); // briefly turn on led + statusLEDModule->setPowerLED(true); wakeCause2 = doLightSleep(100); // leave led on for 1ms secsSlept += sleepTime; @@ -146,7 +146,7 @@ static void lsIdle() } } else { // Time to stop sleeping! - ledBlink.set(false); + statusLEDModule->setPowerLED(false); LOG_INFO("Reached ls_secs, service loop()"); powerFSM.trigger(EVENT_WAKE_TIMER); } diff --git a/src/main.cpp b/src/main.cpp index 08b6707fb..20efb955c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,6 @@ #include "power/PowerHAL.h" #include "FSCommon.h" -#include "Led.h" #include "RTC.h" #include "SPILock.h" #include "Throttle.h" @@ -245,26 +244,8 @@ const char *getDeviceName() return name; } -// TODO remove from main.cpp -static int32_t ledBlinker() -{ - // Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if - // config.device.led_heartbeat_disabled is changed - if (config.device.led_heartbeat_disabled) - return 1000; - - static bool ledOn; - ledOn ^= 1; - - ledBlink.set(ledOn); - - // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that - return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000); -} - uint32_t timeLastPowered = 0; -static Periodic *ledPeriodic; static OSThread *powerFSMthread; static OSThread *ambientLightingThread; @@ -302,21 +283,16 @@ void earlyInitVariant() {} // blink user led in 3 flashes sequence to indicate what is happening void waitUntilPowerLevelSafe() { - -#ifdef LED_PIN - pinMode(LED_PIN, OUTPUT); -#endif - while (powerHAL_isPowerLevelSafe() == false) { -#ifdef LED_PIN +#ifdef LED_POWER // 3x: blink for 300 ms, pause for 300 ms for (int i = 0; i < 3; i++) { - digitalWrite(LED_PIN, LED_STATE_ON); + digitalWrite(LED_POWER, LED_STATE_ON); delay(300); - digitalWrite(LED_PIN, LED_STATE_OFF); + digitalWrite(LED_POWER, LED_STATE_OFF); delay(300); } #endif @@ -340,6 +316,11 @@ void setup() // initialize power HAL layer as early as possible powerHAL_init(); +#ifdef LED_POWER + pinMode(LED_POWER, OUTPUT); + digitalWrite(LED_POWER, LED_STATE_ON); +#endif + // prevent booting if device is in power failure mode // boot sequence will follow when battery level raises to safe mode waitUntilPowerLevelSafe(); @@ -352,11 +333,6 @@ void setup() digitalWrite(PIN_POWER_EN, HIGH); #endif -#ifdef LED_POWER - pinMode(LED_POWER, OUTPUT); - digitalWrite(LED_POWER, LED_STATE_ON); -#endif - #ifdef LED_NOTIFICATION pinMode(LED_NOTIFICATION, OUTPUT); digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON); @@ -487,16 +463,6 @@ void setup() OSThread::setup(); - // TODO make this ifdef based on defined pins and move from main.cpp -#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) - // The ThinkNodes have their own blink logic - // ledPeriodic = new Periodic("Blink", elecrowLedBlinker); -#else - - ledPeriodic = new Periodic("Blink", ledBlinker); - -#endif - fsInit(); #if !MESHTASTIC_EXCLUDE_I2C @@ -721,13 +687,6 @@ void setup() setupSDCard(); #endif - // LED init - -#ifdef LED_PIN - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LED_STATE_ON); // turn on for now -#endif - // Hello printInfo(); #ifdef BUILD_EPOCH @@ -961,12 +920,6 @@ void setup() setupNicheGraphics(); #endif -#ifdef LED_PIN - // Turn LED off after boot, if heartbeat by config - if (config.device.led_heartbeat_disabled) - digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON); -#endif - // Do this after service.init (because that clears error_code) #ifdef HAS_PMU if (!pmu_found) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index ea8d6af8e..912cb467e 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -9,7 +9,6 @@ #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif -#include "Led.h" #include "SPILock.h" #include "power.h" #include "serialization/JSON.h" @@ -92,7 +91,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload); ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks); - ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED); ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport); ResourceNode *nodeJsonNodes = new ResourceNode("/json/nodes", "GET", &handleNodes); ResourceNode *nodeJsonFsBrowseStatic = new ResourceNode("/json/fs/browse/static", "GET", &handleFsBrowseStatic); @@ -110,7 +108,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) secureServer->registerNode(nodeRestart); secureServer->registerNode(nodeFormUpload); secureServer->registerNode(nodeJsonScanNetworks); - secureServer->registerNode(nodeJsonBlinkLED); secureServer->registerNode(nodeJsonFsBrowseStatic); secureServer->registerNode(nodeJsonDelete); secureServer->registerNode(nodeJsonReport); @@ -133,7 +130,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) insecureServer->registerNode(nodeRestart); insecureServer->registerNode(nodeFormUpload); insecureServer->registerNode(nodeJsonScanNetworks); - insecureServer->registerNode(nodeJsonBlinkLED); insecureServer->registerNode(nodeJsonFsBrowseStatic); insecureServer->registerNode(nodeJsonDelete); insecureServer->registerNode(nodeJsonReport); @@ -904,45 +900,6 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res) webServerThread->requestRestart = (millis() / 1000) + 5; } -void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) -{ - res->setHeader("Content-Type", "application/json"); - res->setHeader("Access-Control-Allow-Origin", "*"); - res->setHeader("Access-Control-Allow-Methods", "POST"); - - ResourceParameters *params = req->getParams(); - std::string blink_target; - - if (!params->getQueryParameter("blink_target", blink_target)) { - // if no blink_target was supplied in the URL parameters of the - // POST request, then assume we should blink the LED - blink_target = "LED"; - } - - if (blink_target == "LED") { - uint8_t count = 10; - while (count > 0) { - ledBlink.set(true); - delay(50); - ledBlink.set(false); - delay(50); - count = count - 1; - } - } else { -#if HAS_SCREEN - if (screen) - screen->blink(); -#endif - } - - JSONObject jsonObjOuter; - jsonObjOuter["status"] = new JSONValue("ok"); - JSONValue *value = new JSONValue(jsonObjOuter); - std::string jsonString = value->Stringify(); - res->print(jsonString.c_str()); - delete value; -} - void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "application/json"); diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index 91cad3359..6efdb59b7 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -11,7 +11,6 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res); void handleScanNetworks(HTTPRequest *req, HTTPResponse *res); void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res); void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res); -void handleBlinkLED(HTTPRequest *req, HTTPResponse *res); void handleReport(HTTPRequest *req, HTTPResponse *res); void handleNodes(HTTPRequest *req, HTTPResponse *res); void handleUpdateFs(HTTPRequest *req, HTTPResponse *res); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 61dbc0b92..a4869f708 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -643,12 +643,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) accelerometerThread->enabled = true; accelerometerThread->start(); } -#endif -#ifdef LED_PIN - // Turn LED off if heartbeat by config - if (c.payload_variant.device.led_heartbeat_disabled) { - digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON); - } #endif if (config.device.button_gpio == c.payload_variant.device.button_gpio && config.device.buzzer_gpio == c.payload_variant.device.buzzer_gpio && diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index a73e59ac9..0708afe82 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,9 +1,9 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "buzz/BuzzerFeedbackThread.h" -#include "modules/StatusLEDModule.h" #include "modules/SystemCommandsModule.h" #endif +#include "modules/StatusLEDModule.h" #if !MESHTASTIC_EXCLUDE_REPLYBOT #include "ReplyBotModule.h" #endif @@ -112,9 +112,7 @@ void setupModules() buzzerFeedbackThread = new BuzzerFeedbackThread(); } #endif -#if defined(LED_CHARGE) || defined(LED_PAIRING) statusLEDModule = new StatusLEDModule(); -#endif #if !MESHTASTIC_EXCLUDE_REPLYBOT new ReplyBotModule(); #endif diff --git a/src/modules/PowerStressModule.cpp b/src/modules/PowerStressModule.cpp index d487fe6fc..1c073a10a 100644 --- a/src/modules/PowerStressModule.cpp +++ b/src/modules/PowerStressModule.cpp @@ -1,5 +1,4 @@ #include "PowerStressModule.h" -#include "Led.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerMon.h" @@ -78,10 +77,12 @@ int32_t PowerStressModule::runOnce() switch (p.cmd) { case meshtastic_PowerStressMessage_Opcode_LED_ON: - ledForceOn.set(true); + // FIXME - implement + // ledForceOn.set(true); break; case meshtastic_PowerStressMessage_Opcode_LED_OFF: - ledForceOn.set(false); + // FIXME - implement + // ledForceOn.set(false); break; case meshtastic_PowerStressMessage_Opcode_GPS_ON: // FIXME - implement diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index e7a405bdf..644799c9b 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -13,8 +13,10 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") { bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); powerStatusObserver.observe(&powerStatus->onNewStatus); +#if !MESHTASTIC_EXCLUDE_INPUTBROKER if (inputBroker) inputObserver.observe(inputBroker); +#endif } int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) @@ -62,19 +64,22 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) } return 0; }; - +#if !MESHTASTIC_EXCLUDE_INPUTBROKER int StatusLEDModule::handleInputEvent(const InputEvent *event) { lastUserbuttonTime = millis(); return 0; } +#endif int32_t StatusLEDModule::runOnce() { my_interval = 1000; if (power_state == charging) { +#ifndef POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING CHARGE_LED_state = !CHARGE_LED_state; +#endif } else if (power_state == charged) { CHARGE_LED_state = LED_STATE_ON; } else if (power_state == critical) { @@ -94,7 +99,15 @@ int32_t StatusLEDModule::runOnce() } } else { - CHARGE_LED_state = LED_STATE_OFF; + if (doing_fast_blink) { + CHARGE_LED_state = LED_STATE_OFF; + doing_fast_blink = false; + my_interval = 999; + } else { + CHARGE_LED_state = LED_STATE_ON; + doing_fast_blink = true; + my_interval = 1; + } } if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis() || doing_fast_blink) { @@ -112,6 +125,11 @@ int32_t StatusLEDModule::runOnce() PAIRING_LED_state = LED_STATE_ON; } + // Override if disabled in config + if (config.device.led_heartbeat_disabled) { + CHARGE_LED_state = LED_STATE_OFF; + } +#ifdef Battery_LED_1 bool chargeIndicatorLED1 = LED_STATE_OFF; bool chargeIndicatorLED2 = LED_STATE_OFF; bool chargeIndicatorLED3 = LED_STATE_OFF; @@ -126,9 +144,23 @@ int32_t StatusLEDModule::runOnce() if (powerStatus && powerStatus->getBatteryChargePercent() >= 75) chargeIndicatorLED4 = LED_STATE_ON; } +#endif -#ifdef LED_CHARGE - digitalWrite(LED_CHARGE, CHARGE_LED_state); +#if defined(HAS_PMU) + if (pmu_found && PMU) { + // blink the axp led + PMU->setChargingLedMode(CHARGE_LED_state ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); + } +#endif + +#ifdef PCA_LED_POWER + io.digitalWrite(PCA_LED_POWER, CHARGE_LED_state); +#endif +#ifdef PCA_LED_ENABLE + io.digitalWrite(PCA_LED_ENABLE, CHARGE_LED_state); +#endif +#ifdef LED_POWER + digitalWrite(LED_POWER, CHARGE_LED_state); #endif #ifdef LED_PAIRING digitalWrite(LED_PAIRING, PAIRING_LED_state); @@ -149,3 +181,43 @@ int32_t StatusLEDModule::runOnce() return (my_interval); } + +void StatusLEDModule::setPowerLED(bool LEDon) +{ + +#if defined(HAS_PMU) + if (pmu_found && PMU) { + // blink the axp led + PMU->setChargingLedMode(LEDon ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); + } +#endif + if (LEDon) + LEDon = LED_STATE_ON; + else + LEDon = LED_STATE_OFF; +#ifdef PCA_LED_POWER + io.digitalWrite(PCA_LED_POWER, LEDon); +#endif +#ifdef PCA_LED_ENABLE + io.digitalWrite(PCA_LED_ENABLE, LEDon); +#endif +#ifdef LED_POWER + digitalWrite(LED_POWER, LEDon); +#endif +#ifdef LED_PAIRING + digitalWrite(LED_PAIRING, LEDon); +#endif + +#ifdef Battery_LED_1 + digitalWrite(Battery_LED_1, LEDon); +#endif +#ifdef Battery_LED_2 + digitalWrite(Battery_LED_2, LEDon); +#endif +#ifdef Battery_LED_3 + digitalWrite(Battery_LED_3, LEDon); +#endif +#ifdef Battery_LED_4 + digitalWrite(Battery_LED_4, LEDon); +#endif +} diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index 98020cb32..c35f29019 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -5,10 +5,14 @@ #include "PowerStatus.h" #include "concurrency/OSThread.h" #include "configuration.h" -#include "input/InputBroker.h" +#include "main.h" #include #include +#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#include "input/InputBroker.h" +#endif + class StatusLEDModule : private concurrency::OSThread { bool slowTrack = false; @@ -17,8 +21,11 @@ class StatusLEDModule : private concurrency::OSThread StatusLEDModule(); int handleStatusUpdate(const meshtastic::Status *); - +#if !MESHTASTIC_EXCLUDE_INPUTBROKER int handleInputEvent(const InputEvent *arg); +#endif + + void setPowerLED(bool); protected: unsigned int my_interval = 1000; // interval in millisconds @@ -28,8 +35,10 @@ class StatusLEDModule : private concurrency::OSThread CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); CallbackObserver powerStatusObserver = CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); +#if !MESHTASTIC_EXCLUDE_INPUTBROKER CallbackObserver inputObserver = CallbackObserver(this, &StatusLEDModule::handleInputEvent); +#endif private: bool CHARGE_LED_state = LED_STATE_OFF; diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index d1965f03e..f73a5b896 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -158,7 +158,7 @@ #endif #ifdef PIN_LED1 -#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK +#define LED_POWER PIN_LED1 // LED1 on nrf52840-DK #endif #ifdef PIN_BUTTON1 diff --git a/src/sleep.cpp b/src/sleep.cpp index 8b30a5352..7c768d573 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -5,7 +5,6 @@ #endif #include "Default.h" -#include "Led.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -13,6 +12,7 @@ #include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" +#include "modules/StatusLEDModule.h" #include "sleep.h" #include "target_specific.h" @@ -268,8 +268,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN digitalWrite(PIN_WD_EN, LOW); #endif #endif - ledBlink.set(false); - + statusLEDModule->setPowerLED(false); #ifdef RESET_OLED digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power #endif diff --git a/variants/esp32/betafpv_2400_tx_micro/variant.h b/variants/esp32/betafpv_2400_tx_micro/variant.h index 67699e7c8..8e875a3e6 100644 --- a/variants/esp32/betafpv_2400_tx_micro/variant.h +++ b/variants/esp32/betafpv_2400_tx_micro/variant.h @@ -14,7 +14,7 @@ #define LORA_CS 5 #define RF95_FAN_EN 17 -// #define LED_PIN 16 // This is a LED_WS2812 not a standard LED +// This is a LED_WS2812 not a standard LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected #define NEOPIXEL_DATA 16 // gpio pin used to send data to the neopixels diff --git a/variants/esp32/betafpv_900_tx_nano/variant.h b/variants/esp32/betafpv_900_tx_nano/variant.h index 7a4ae9190..6bee74d90 100644 --- a/variants/esp32/betafpv_900_tx_nano/variant.h +++ b/variants/esp32/betafpv_900_tx_nano/variant.h @@ -20,7 +20,7 @@ #define LORA_DIO2 #define LORA_DIO3 -#define LED_PIN 16 // green - blue is at 17 +#define LED_POWER 16 // green - blue is at 17 #define BUTTON_PIN 25 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index 28ce64f91..e91e4dcef 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -23,8 +23,6 @@ #define SX126X_TXEN RADIOLIB_NC #define SX126X_RXEN RADIOLIB_NC -// Status -// #define LED_PIN 1 // External notification // FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the // app/preferences diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h index 037933140..1f84fffa1 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h @@ -21,8 +21,8 @@ #define BUTTON_PIN 15 // Right side button - if not available, set device.button_gpio to 0 from Meshtastic client // LEDs -#define LED_PIN 13 // Tx LED -#define USER_LED 2 // Rx LED +#define LED_POWER 13 // Tx LED +#define USER_LED 2 // Rx LED // Buzzer #define PIN_BUZZER 33 diff --git a/variants/esp32/diy/hydra/variant.h b/variants/esp32/diy/hydra/variant.h index e5c10e26b..68194f869 100644 --- a/variants/esp32/diy/hydra/variant.h +++ b/variants/esp32/diy/hydra/variant.h @@ -15,7 +15,7 @@ #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) #define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). -#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets) +#define LED_POWER 2 // add status LED (compatible with core-pcb and DIY targets) // Radio #define USE_SX1262 // E22-900M30S uses SX1262 diff --git a/variants/esp32/diy/v1/variant.h b/variants/esp32/diy/v1/variant.h index 8a2df3f2b..862969af0 100644 --- a/variants/esp32/diy/v1/variant.h +++ b/variants/esp32/diy/v1/variant.h @@ -15,7 +15,7 @@ #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) #define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). -#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets) +#define LED_POWER 2 // add status LED (compatible with core-pcb and DIY targets) #define LORA_DIO0 26 // a No connect on the SX1262/SX1268 module #define LORA_RESET 23 // RST for SX1276, and for SX1262/SX1268 diff --git a/variants/esp32/hackerboxes_esp32_io/variant.h b/variants/esp32/hackerboxes_esp32_io/variant.h index 06f0032ee..42ce2423e 100644 --- a/variants/esp32/hackerboxes_esp32_io/variant.h +++ b/variants/esp32/hackerboxes_esp32_io/variant.h @@ -3,7 +3,7 @@ // HACKBOX LoRa IO Kit // Uses a ESP-32-WROOM and a RA-01SH (SX1262) LoRa Board -#define LED_PIN 2 // LED +#define LED_POWER 2 // LED #define LED_STATE_ON 1 // State when LED is lit #define HAS_SCREEN 0 diff --git a/variants/esp32/heltec_v1/variant.h b/variants/esp32/heltec_v1/variant.h index d1338a28e..c4f6577a8 100644 --- a/variants/esp32/heltec_v1/variant.h +++ b/variants/esp32/heltec_v1/variant.h @@ -12,7 +12,7 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define USE_RF95 diff --git a/variants/esp32/heltec_v2.1/variant.h b/variants/esp32/heltec_v2.1/variant.h index 8ebccc54f..e4aeb363d 100644 --- a/variants/esp32/heltec_v2.1/variant.h +++ b/variants/esp32/heltec_v2.1/variant.h @@ -18,7 +18,7 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define USE_RF95 diff --git a/variants/esp32/heltec_v2/variant.h b/variants/esp32/heltec_v2/variant.h index 5c183818b..c35465f81 100644 --- a/variants/esp32/heltec_v2/variant.h +++ b/variants/esp32/heltec_v2/variant.h @@ -13,7 +13,7 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define USE_RF95 diff --git a/variants/esp32/heltec_wireless_bridge/variant.h b/variants/esp32/heltec_wireless_bridge/variant.h index 5ad16d0e2..82cc83aa8 100644 --- a/variants/esp32/heltec_wireless_bridge/variant.h +++ b/variants/esp32/heltec_wireless_bridge/variant.h @@ -15,7 +15,7 @@ #undef GPS_TX_PIN // Green / Lora = PIN 22 / GPIO2, Yellow / Wifi = PIN 23 / GPIO0, Blue / BLE = PIN 25 / GPIO16 -#define LED_PIN 22 +#define LED_POWER 22 #define WIFI_LED 23 #define BLE_LED 25 diff --git a/variants/esp32/heltec_wsl_v2.1/variant.h b/variants/esp32/heltec_wsl_v2.1/variant.h index 3927a89d6..db374afb6 100644 --- a/variants/esp32/heltec_wsl_v2.1/variant.h +++ b/variants/esp32/heltec_wsl_v2.1/variant.h @@ -1,7 +1,7 @@ #define I2C_SCL SCL #define I2C_SDA SDA -#define LED_PIN LED +#define LED_POWER LED // active low, powers the Battery reader, but no lora antenna boost (?) // #define VEXT_ENABLE Vext diff --git a/variants/esp32/m5stack_coreink/variant.h b/variants/esp32/m5stack_coreink/variant.h index 9bf45f2ff..84a1e1966 100644 --- a/variants/esp32/m5stack_coreink/variant.h +++ b/variants/esp32/m5stack_coreink/variant.h @@ -11,7 +11,7 @@ // Green LED #define LED_STATE_ON 1 // State when LED is lit -#define LED_PIN 10 +#define LED_POWER 10 // PCF8563 RTC Module #define PCF8563_RTC 0x51 diff --git a/variants/esp32/radiomaster_900_bandit_nano/variant.h b/variants/esp32/radiomaster_900_bandit_nano/variant.h index 1b6bba126..318401f92 100644 --- a/variants/esp32/radiomaster_900_bandit_nano/variant.h +++ b/variants/esp32/radiomaster_900_bandit_nano/variant.h @@ -37,7 +37,7 @@ /* LED PIN setup. */ -#define LED_PIN 15 +#define LED_POWER 15 /* Five way button when using ADC. diff --git a/variants/esp32/rak11200/variant.h b/variants/esp32/rak11200/variant.h index fe7d05676..a38ac83b7 100644 --- a/variants/esp32/rak11200/variant.h +++ b/variants/esp32/rak11200/variant.h @@ -43,7 +43,7 @@ static const uint8_t SCK = 33; #undef GPS_TX_PIN #define GPS_TX_PIN (TX1) -#define LED_PIN LED_BLUE +#define LED_POWER LED_BLUE #define PIN_VBAT WB_A0 #define BATTERY_PIN PIN_VBAT diff --git a/variants/esp32/tbeam/variant.h b/variants/esp32/tbeam/variant.h index 2c1e61c49..cca52cb9a 100644 --- a/variants/esp32/tbeam/variant.h +++ b/variants/esp32/tbeam/variant.h @@ -9,7 +9,7 @@ #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. #define LED_STATE_ON 0 // State when LED is lit -#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4 +#define LED_POWER 4 // Newer tbeams (1.1) have an extra led on GPIO4 // TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if // not found then probe for SX1262 @@ -49,7 +49,7 @@ #undef EXT_NOTIFY_OUT #undef LED_STATE_ON -#undef LED_PIN +#undef LED_POWER #define HAS_CST226SE 1 #define HAS_TOUCHSCREEN 1 diff --git a/variants/esp32/tlora_v1/variant.h b/variants/esp32/tlora_v1/variant.h index 83e2c193e..ff9f4a8ef 100644 --- a/variants/esp32/tlora_v1/variant.h +++ b/variants/esp32/tlora_v1/variant.h @@ -5,7 +5,7 @@ #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost #define VEXT_ON_VALUE LOW -#define LED_PIN 2 // If defined we will blink this LED +#define LED_POWER 2 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define BUTTON_NEED_PULLUP #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. diff --git a/variants/esp32/tlora_v1_3/variant.h b/variants/esp32/tlora_v1_3/variant.h index 73cb31f27..2b0395d8a 100644 --- a/variants/esp32/tlora_v1_3/variant.h +++ b/variants/esp32/tlora_v1_3/variant.h @@ -7,7 +7,7 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 36 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32/tlora_v2/variant.h b/variants/esp32/tlora_v2/variant.h index 8a7cf89ec..099fdc2ee 100644 --- a/variants/esp32/tlora_v2/variant.h +++ b/variants/esp32/tlora_v2/variant.h @@ -5,7 +5,7 @@ #define I2C_SCL 22 #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN \ 0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one // between this pin and ground diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini index dfdbcb152..2ea9bbb50 100644 --- a/variants/esp32/tlora_v2_1_16/platformio.ini +++ b/variants/esp32/tlora_v2_1_16/platformio.ini @@ -22,4 +22,4 @@ build_flags = ${env:tlora-v2-1-1_6.build_flags} -DBUTTON_PIN=0 -DPIN_BUZZER=25 - -DLED_PIN=-1 \ No newline at end of file + -DLED_POWER=-1 \ No newline at end of file diff --git a/variants/esp32/tlora_v2_1_16/variant.h b/variants/esp32/tlora_v2_1_16/variant.h index 9584dd68b..5488fddf4 100644 --- a/variants/esp32/tlora_v2_1_16/variant.h +++ b/variants/esp32/tlora_v2_1_16/variant.h @@ -8,10 +8,10 @@ #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 -#if defined(LED_PIN) && LED_PIN == -1 -#undef LED_PIN +#if defined(LED_POWER) && LED_POWER == -1 +#undef LED_POWER #else -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #endif #define USE_RF95 diff --git a/variants/esp32/tlora_v2_1_18/variant.h b/variants/esp32/tlora_v2_1_18/variant.h index efc676992..1ab08c364 100644 --- a/variants/esp32/tlora_v2_1_18/variant.h +++ b/variants/esp32/tlora_v2_1_18/variant.h @@ -6,7 +6,7 @@ #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 12 // If defined, this will be used for user button presses, #define BUTTON_NEED_PULLUP diff --git a/variants/esp32/trackerd/variant.h b/variants/esp32/trackerd/variant.h index 0996e85ac..8071ba99d 100644 --- a/variants/esp32/trackerd/variant.h +++ b/variants/esp32/trackerd/variant.h @@ -8,7 +8,7 @@ #define GPS_RX_PIN 9 #define GPS_TX_PIN 10 -#define LED_PIN 13 // 13 red, 2 blue, 15 red +#define LED_POWER 13 // 13 red, 2 blue, 15 red #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32c3/ai-c3/variant.h b/variants/esp32c3/ai-c3/variant.h index 6c4f4d38a..a933de76b 100644 --- a/variants/esp32c3/ai-c3/variant.h +++ b/variants/esp32c3/ai-c3/variant.h @@ -4,7 +4,7 @@ #define I2C_SCL SCL #define BUTTON_PIN 9 // BOOT button -#define LED_PIN 30 // RGB LED +#define LED_POWER 30 // RGB LED #define USE_RF95 #define LORA_SCK 4 diff --git a/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h b/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h index 7432a9941..71090fbeb 100644 --- a/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h +++ b/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h @@ -3,7 +3,7 @@ // Hackerboxes LoRa ESP32-C3 OLED Kit // Uses a ESP32-C3 OLED Board and a RA-01SH (SX1262) LoRa Board -#define LED_PIN 8 // LED +#define LED_POWER 8 // LED #define LED_STATE_ON 1 // State when LED is lit #define HAS_SCREEN 0 diff --git a/variants/esp32c3/heltec_esp32c3/variant.h b/variants/esp32c3/heltec_esp32c3/variant.h index ca00c43fa..ed2f6f878 100644 --- a/variants/esp32c3/heltec_esp32c3/variant.h +++ b/variants/esp32c3/heltec_esp32c3/variant.h @@ -3,7 +3,7 @@ // LED pin on HT-DEV-ESP_V2 and HT-DEV-ESP_V3 // https://resource.heltec.cn/download/HT-CT62/HT-CT62_Reference_Design.pdf // https://resource.heltec.cn/download/HT-DEV-ESP/HT-DEV-ESP_V3_Sch.pdf -#define LED_PIN 2 // LED +#define LED_POWER 2 // LED #define LED_STATE_ON 1 // State when LED is lit #define HAS_SCREEN 0 diff --git a/variants/esp32c6/tlora_c6/variant.h b/variants/esp32c6/tlora_c6/variant.h index 4a0d40232..fcc5b9813 100644 --- a/variants/esp32c6/tlora_c6/variant.h +++ b/variants/esp32c6/tlora_c6/variant.h @@ -1,7 +1,7 @@ #define I2C_SDA 8 // I2C pins for this board #define I2C_SCL 9 -#define LED_PIN 7 // If defined we will blink this LED +#define LED_POWER 7 // If defined we will blink this LED #define LED_STATE_ON 0 // State when LED is lit #define USE_SX1262 diff --git a/variants/esp32s2/nugget_s2_lora/variant.h b/variants/esp32s2/nugget_s2_lora/variant.h index 2d123d603..9d88882e5 100644 --- a/variants/esp32s2/nugget_s2_lora/variant.h +++ b/variants/esp32s2/nugget_s2_lora/variant.h @@ -1,7 +1,7 @@ #define I2C_SDA 34 // I2C pins for this board #define I2C_SCL 36 -#define LED_PIN 15 // If defined we will blink this LED +#define LED_POWER 15 // If defined we will blink this LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 3 // How many neopixels are connected diff --git a/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h b/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h index 1591f6395..5decc7eb2 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h +++ b/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h @@ -1,7 +1,7 @@ // EByte EoRA-Hub // Uses E80 (LR1121) LoRa module -#define LED_PIN 35 +#define LED_POWER 35 // Button - user interface #define BUTTON_PIN 0 // BOOT button diff --git a/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h index 5da99667b..85321cbe0 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h +++ b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h @@ -1,5 +1,5 @@ // LED - status indication -#define LED_PIN 37 +#define LED_POWER 37 // Button - user interface #define BUTTON_PIN 0 // This is the BOOT button, and it has its own pull-up resistor diff --git a/variants/esp32s3/EBYTE_ESP32-S3/variant.h b/variants/esp32s3/EBYTE_ESP32-S3/variant.h index 80fb26434..6dbe6231c 100644 --- a/variants/esp32s3/EBYTE_ESP32-S3/variant.h +++ b/variants/esp32s3/EBYTE_ESP32-S3/variant.h @@ -100,7 +100,7 @@ */ // Status -#define LED_PIN 1 +#define LED_POWER 1 #define LED_STATE_ON 1 // State when LED is lit // External notification // FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the diff --git a/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h index ff4f883fe..8768fd70e 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h @@ -1,5 +1,5 @@ // Status -#define LED_PIN 1 +#define LED_POWER 1 #define PIN_BUTTON1 47 // 功能键 #define PIN_BUTTON2 4 // 电源键 diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp index 4b485a1a3..82b218cf9 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp @@ -8,5 +8,11 @@ void earlyInitVariant() Wire.begin(48, 47); io.pinMode(PCA_PIN_EINK_EN, OUTPUT); io.pinMode(PCA_PIN_POWER_EN, OUTPUT); + io.pinMode(PCA_LED_POWER, OUTPUT); + io.pinMode(PCA_LED_USER, OUTPUT); + io.pinMode(PCA_LED_ENABLE, OUTPUT); + io.digitalWrite(PCA_PIN_POWER_EN, HIGH); + io.digitalWrite(PCA_LED_USER, LOW); + io.digitalWrite(PCA_LED_ENABLE, LOW); } diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 353741d91..4f6e415a2 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -8,8 +8,10 @@ // LED // Both of these are on the GPIO expander -#define PCA_LED_USER 1 // the Blue LED -#define PCA_LED_POWER 3 // the Red LED? Seems to have hardware logic to blink when USB is plugged in. +#define PCA_LED_USER 1 // the Blue LED +#define PCA_LED_ENABLE 2 // the power supply to the LEDs, in an OR arrangement with VBUS power +#define PCA_LED_POWER 3 // the Red LED? Seems to have hardware logic to blink when USB is plugged in. +#define POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING // USB_CHECK #define EXT_PWR_DETECT 12 diff --git a/variants/esp32s3/bpi_picow_esp32_s3/variant.h b/variants/esp32s3/bpi_picow_esp32_s3/variant.h index d8d9413d7..d3e573645 100644 --- a/variants/esp32s3/bpi_picow_esp32_s3/variant.h +++ b/variants/esp32s3/bpi_picow_esp32_s3/variant.h @@ -11,7 +11,7 @@ #define I2C_SDA 12 #define I2C_SCL 14 -#define LED_PIN 46 +#define LED_POWER 46 #define LED_STATE_ON 0 // State when LED is litted // #define BUTTON_PIN 15 // Pico OLED 1.3 User key 0 - removed User key 1 (17) diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h index 360e33481..c9200b96b 100644 --- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h +++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h @@ -26,7 +26,7 @@ // #define GPS_RX_PIN 44 // #define GPS_TX_PIN 43 -#define LED_PIN 41 +#define LED_POWER 41 #define BUTTON_PIN 2 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h b/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h index 024f912dd..54db932ea 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h @@ -11,7 +11,7 @@ #define I2C_SDA 18 // 1 // I2C pins for this board #define I2C_SCL 17 // 2 -// #define LED_PIN 38 // This is a RGB LED not a standard LED +// #define LED_POWER 38 // This is a RGB LED not a standard LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected #define NEOPIXEL_DATA 38 // gpio pin used to send data to the neopixels diff --git a/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h b/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h index 8a3a39003..20e058058 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h +++ b/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h @@ -11,7 +11,7 @@ #define I2C_SDA 18 // 1 // I2C pins for this board #define I2C_SCL 17 // 2 -// #define LED_PIN 38 // This is a RGB LED not a standard LED +// #define LED_POWER 38 // This is a RGB LED not a standard LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected #define NEOPIXEL_DATA 38 // gpio pin used to send data to the neopixels diff --git a/variants/esp32s3/dreamcatcher/variant.h b/variants/esp32s3/dreamcatcher/variant.h index 7835979e1..963aff477 100644 --- a/variants/esp32s3/dreamcatcher/variant.h +++ b/variants/esp32s3/dreamcatcher/variant.h @@ -6,7 +6,7 @@ #define I2C_SDA1 45 #define I2C_SCL1 46 -#define LED_PIN 6 +#define LED_POWER 6 #define LED_STATE_ON 1 #define BUTTON_PIN 0 diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h index b30b7fc3e..3ee5545a8 100644 --- a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h +++ b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h @@ -1,5 +1,5 @@ -#define LED_PIN 33 -#define LED_PIN2 34 +#define LED_POWER 33 +#define LED_POWER2 34 #define EXT_PWR_DETECT 35 #define BUTTON_PIN 18 diff --git a/variants/esp32s3/heltec_v3/variant.h b/variants/esp32s3/heltec_v3/variant.h index d760c3b7f..d2d904d9c 100644 --- a/variants/esp32s3/heltec_v3/variant.h +++ b/variants/esp32s3/heltec_v3/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN LED +#define LED_POWER LED #define USE_SSD1306 // Heltec_v3 has a SSD1306 display diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 20b3b4606..8b951b5e9 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -26,7 +26,7 @@ build_flags = ${heltec_v4_base.build_flags} -D HELTEC_V4_OLED -D USE_SSD1306 ; Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver) - -D LED_PIN=35 + -D LED_POWER=35 -D RESET_OLED=21 -D I2C_SDA=17 -D I2C_SCL=18 diff --git a/variants/esp32s3/heltec_vision_master_e213/variant.h b/variants/esp32s3/heltec_vision_master_e213/variant.h index 60f4e00cc..c9aaa2ee8 100644 --- a/variants/esp32s3/heltec_vision_master_e213/variant.h +++ b/variants/esp32s3/heltec_vision_master_e213/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 45 // LED is not populated on earliest board variant +#define LED_POWER 45 // LED is not populated on earliest board variant #define BUTTON_PIN 0 #define PIN_BUTTON2 21 // Second built-in button #define ALT_BUTTON_PIN PIN_BUTTON2 // Send the up event diff --git a/variants/esp32s3/heltec_vision_master_e290/variant.h b/variants/esp32s3/heltec_vision_master_e290/variant.h index d7bae7dc2..b32715e39 100644 --- a/variants/esp32s3/heltec_vision_master_e290/variant.h +++ b/variants/esp32s3/heltec_vision_master_e290/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 45 // LED is not populated on earliest board variant +#define LED_POWER 45 // LED is not populated on earliest board variant #define BUTTON_PIN 0 #define PIN_BUTTON2 21 // Second built-in button #define ALT_BUTTON_PIN PIN_BUTTON2 // Send the up event diff --git a/variants/esp32s3/heltec_wireless_paper/variant.h b/variants/esp32s3/heltec_wireless_paper/variant.h index bbfd54ada..7f57bb67f 100644 --- a/variants/esp32s3/heltec_wireless_paper/variant.h +++ b/variants/esp32s3/heltec_wireless_paper/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define BUTTON_PIN 0 // I2C diff --git a/variants/esp32s3/heltec_wireless_paper_v1/variant.h b/variants/esp32s3/heltec_wireless_paper_v1/variant.h index 4505395c9..59dd485f6 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/variant.h +++ b/variants/esp32s3/heltec_wireless_paper_v1/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define BUTTON_PIN 0 // I2C diff --git a/variants/esp32s3/heltec_wireless_tracker/variant.h b/variants/esp32s3/heltec_wireless_tracker/variant.h index 3b19f5afd..b40e40011 100644 --- a/variants/esp32s3/heltec_wireless_tracker/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define _VARIANT_HELTEC_WIRELESS_TRACKER #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h index df5ab4716..e7d3f93c1 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h index a5489173d..0ca2dfc03 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define _VARIANT_HELTEC_WIRELESS_TRACKER diff --git a/variants/esp32s3/heltec_wsl_v3/variant.h b/variants/esp32s3/heltec_wsl_v3/variant.h index c103b9172..c81f45d3b 100644 --- a/variants/esp32s3/heltec_wsl_v3/variant.h +++ b/variants/esp32s3/heltec_wsl_v3/variant.h @@ -1,7 +1,7 @@ #define I2C_SCL SCL #define I2C_SDA SDA -#define LED_PIN LED +#define LED_POWER LED #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost #define VEXT_ON_VALUE LOW diff --git a/variants/esp32s3/mesh-tab/variant.h b/variants/esp32s3/mesh-tab/variant.h index 99204bba3..30042b90f 100644 --- a/variants/esp32s3/mesh-tab/variant.h +++ b/variants/esp32s3/mesh-tab/variant.h @@ -13,7 +13,7 @@ #define ADC_CHANNEL ADC1_GPIO4_CHANNEL // LED -#define LED_PIN 21 +#define LED_POWER 21 // Button #define BUTTON_PIN 0 diff --git a/variants/esp32s3/nibble_esp32/variant.h b/variants/esp32s3/nibble_esp32/variant.h index 8ffbd9d59..8d75a4fbf 100644 --- a/variants/esp32s3/nibble_esp32/variant.h +++ b/variants/esp32s3/nibble_esp32/variant.h @@ -1,7 +1,7 @@ #define I2C_SDA 11 // I2C pins for this board #define I2C_SCL 10 -#define LED_PIN 1 // If defined we will blink this LED +#define LED_POWER 1 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/nugget_s3_lora/variant.h b/variants/esp32s3/nugget_s3_lora/variant.h index 1354d0837..633ed27f6 100644 --- a/variants/esp32s3/nugget_s3_lora/variant.h +++ b/variants/esp32s3/nugget_s3_lora/variant.h @@ -4,7 +4,7 @@ #define USE_SSD1306 #define DISPLAY_FLIP_SCREEN -#define LED_PIN 15 // If defined we will blink this LED +#define LED_POWER 15 // If defined we will blink this LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 3 // How many neopixels are connected diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index 6431f1fd0..ee0fff524 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -24,7 +24,7 @@ #define PIN_LED1 LED_GREEN #define LED_NOTIFICATION LED_BLUE -#define LED_PIN LED_GREEN +#define LED_POWER LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index 7d263165c..90cb12053 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -32,7 +32,7 @@ #define PIN_LED1 LED_GREEN #define LED_NOTIFICATION LED_BLUE -#define LED_PIN LED_GREEN +#define LED_POWER LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/esp32s3/seeed_xiao_s3/variant.h b/variants/esp32s3/seeed_xiao_s3/variant.h index d8dcbc8d4..11bf48521 100644 --- a/variants/esp32s3/seeed_xiao_s3/variant.h +++ b/variants/esp32s3/seeed_xiao_s3/variant.h @@ -30,7 +30,7 @@ Expansion Board Infomation : https://www.seeedstudio.com/Seeeduino-XIAO-Expansio L76K GPS Module Information : https://www.seeedstudio.com/L76K-GNSS-Module-for-Seeed-Studio-XIAO-p-5864.html */ -#define LED_PIN 48 +#define LED_POWER 48 #define LED_STATE_ON 1 // State when LED is lit #define BUTTON_PIN 21 // This is the Program Button diff --git a/variants/esp32s3/t-beam-1w/variant.h b/variants/esp32s3/t-beam-1w/variant.h index dbe1620e2..f5345de79 100644 --- a/variants/esp32s3/t-beam-1w/variant.h +++ b/variants/esp32s3/t-beam-1w/variant.h @@ -67,7 +67,7 @@ #endif // LED -#define LED_PIN 18 +#define LED_POWER 18 #define LED_STATE_ON 1 // HIGH = ON // Battery ADC diff --git a/variants/esp32s3/t-eth-elite/variant.h b/variants/esp32s3/t-eth-elite/variant.h index b7ac05872..8f2748c36 100644 --- a/variants/esp32s3/t-eth-elite/variant.h +++ b/variants/esp32s3/t-eth-elite/variant.h @@ -12,7 +12,7 @@ #define HAS_SCREEN 1 // Allow for OLED Screens on I2C Header of shield -#define LED_PIN 38 // If defined we will blink this LED +#define LED_POWER 38 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses, #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/tlora_t3s3_epaper/variant.h b/variants/esp32s3/tlora_t3s3_epaper/variant.h index 1ed505420..0f4875fc4 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/variant.h +++ b/variants/esp32s3/tlora_t3s3_epaper/variant.h @@ -22,7 +22,7 @@ #define GPS_RX_PIN 44 #define GPS_TX_PIN 43 -#define LED_PIN 37 +#define LED_POWER 37 #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/tlora_t3s3_v1/variant.h b/variants/esp32s3/tlora_t3s3_v1/variant.h index babe44a58..02e2a0e42 100644 --- a/variants/esp32s3/tlora_t3s3_v1/variant.h +++ b/variants/esp32s3/tlora_t3s3_v1/variant.h @@ -14,7 +14,7 @@ #define I2C_SDA1 43 #define I2C_SCL1 44 -#define LED_PIN 37 // If defined we will blink this LED +#define LED_POWER 37 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses, #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h index ba3e281c8..f9a20c901 100644 --- a/variants/esp32s3/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h index a9bb89d68..029f7753b 100644 --- a/variants/esp32s3/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h index 689864b32..1f1fbbaa1 100644 --- a/variants/esp32s3/tracksenger/oled/variant.h +++ b/variants/esp32s3/tracksenger/oled/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/unphone/variant.h b/variants/esp32s3/unphone/variant.h index 6f0710d62..268eedea5 100644 --- a/variants/esp32s3/unphone/variant.h +++ b/variants/esp32s3/unphone/variant.h @@ -54,7 +54,7 @@ #define SD_SPI_FREQUENCY 25000000 -#define LED_PIN 13 // the red part of the RGB LED +#define LED_POWER 13 // the red part of the RGB LED #define LED_STATE_ON 0 // State when LED is lit #define ALT_BUTTON_PIN 21 // Button 3 - square - top button in landscape mode diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index 50944b6d7..7766a171e 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -52,7 +52,6 @@ extern "C" { // LED #define LED_RED 33 #define LED_POWER LED_RED -#define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED #define LED_GREEN 35 #define LED_NOTIFICATION LED_GREEN #define LED_BLUE 37 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index bc0381a48..a43755c06 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(LED_CHARGE, OUTPUT); - ledOff(LED_CHARGE); - pinMode(LED_PAIRING, OUTPUT); ledOff(LED_PAIRING); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index ba6aa14ab..bdc52a530 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -41,7 +41,7 @@ extern "C" { // LEDs #define LED_BLUE -1 -#define LED_CHARGE (12) +#define LED_POWER (12) #define LED_PAIRING (7) #define LED_NOTIFICATION LED_PAIRING diff --git a/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp b/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp index 35dc1d39b..5972861d6 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp +++ b/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); } \ No newline at end of file diff --git a/variants/nrf52840/ME25LS01-4Y10TD/variant.h b/variants/nrf52840/ME25LS01-4Y10TD/variant.h index a920b02e5..1d1af37ad 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD/variant.h @@ -50,7 +50,7 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp index 35dc1d39b..5972861d6 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); } \ No newline at end of file diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h index 683669160..a5bb53a33 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h @@ -50,7 +50,7 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/MS24SF1/variant.h b/variants/nrf52840/MS24SF1/variant.h index a71be99fd..a41b3a350 100644 --- a/variants/nrf52840/MS24SF1/variant.h +++ b/variants/nrf52840/MS24SF1/variant.h @@ -50,7 +50,7 @@ extern "C" { #define PIN_LED1 (-1) -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/seeed_solar_node/variant.cpp b/variants/nrf52840/seeed_solar_node/variant.cpp index 994e97ff9..4123944d4 100644 --- a/variants/nrf52840/seeed_solar_node/variant.cpp +++ b/variants/nrf52840/seeed_solar_node/variant.cpp @@ -101,7 +101,6 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); digitalWrite(PIN_LED2, LOW); pinMode(PIN_LED2, OUTPUT); - // digitalWrite(LED_PIN, LOW); pinMode(GPS_EN, OUTPUT); digitalWrite(GPS_EN, HIGH); diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index 0d599d313..8615ca22e 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -65,7 +65,7 @@ static const uint8_t A5 = PIN_A5; #define LED_GREEN (13) #define LED_BLUE (12) -#define PIN_LED1 LED_GREEN // PIN_LED1 is used in src/platform/nrf52/architecture.h to define LED_PIN +#define PIN_LED1 LED_GREEN // PIN_LED1 is used in src/platform/nrf52/architecture.h to define LED_POWER #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED diff --git a/variants/nrf52840/tracker-t1000-e/variant.cpp b/variants/nrf52840/tracker-t1000-e/variant.cpp index 8096705d0..1d0423b9b 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.cpp +++ b/variants/nrf52840/tracker-t1000-e/variant.cpp @@ -32,10 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index 143b7d4ad..e258f63ae 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -47,7 +47,7 @@ extern "C" { #define PIN_3V3_ACC_EN (32 + 7) // P1.7, Power to Acc #define PIN_LED1 (0 + 24) // P0.24 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/wio-t1000-s/variant.cpp b/variants/nrf52840/wio-t1000-s/variant.cpp index 85e0c44f3..3c54f6ce5 100644 --- a/variants/nrf52840/wio-t1000-s/variant.cpp +++ b/variants/nrf52840/wio-t1000-s/variant.cpp @@ -32,10 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/wio-t1000-s/variant.h b/variants/nrf52840/wio-t1000-s/variant.h index 3b8103d85..a55c42775 100644 --- a/variants/nrf52840/wio-t1000-s/variant.h +++ b/variants/nrf52840/wio-t1000-s/variant.h @@ -47,7 +47,7 @@ extern "C" { #define PIN_3V3_ACC_EN (32 + 7) // P1.7, Power to Acc #define PIN_LED1 (0 + 24) // P0.24 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h index b27cc0297..ee86e1a34 100644 --- a/variants/rp2040/challenger_2040_lora/pins_arduino.h +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -7,7 +7,7 @@ #define ADC_RESOLUTION (12u) // LEDs -#define LED_PIN (24u) +#define LED_POWER (24u) // Serial #define PIN_SERIAL1_TX (16u) diff --git a/variants/rp2040/ec_catsniffer/variant.h b/variants/rp2040/ec_catsniffer/variant.h index 400074e59..7df69f134 100644 --- a/variants/rp2040/ec_catsniffer/variant.h +++ b/variants/rp2040/ec_catsniffer/variant.h @@ -9,7 +9,7 @@ #undef GPS_RX_PIN #undef GPS_TX_PIN -#define LED_PIN 27 +#define LED_POWER 27 #define USE_SX1262 diff --git a/variants/rp2040/feather_rp2040_rfm95/variant.h b/variants/rp2040/feather_rp2040_rfm95/variant.h index e9e178202..efaced7b4 100644 --- a/variants/rp2040/feather_rp2040_rfm95/variant.h +++ b/variants/rp2040/feather_rp2040_rfm95/variant.h @@ -20,7 +20,7 @@ #define BUTTON_PIN 7 // #define BUTTON_NEED_PULLUP -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED // #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/nibble_rp2040/variant.h b/variants/rp2040/nibble_rp2040/variant.h index 0f71b98e9..3b1dfcd7b 100644 --- a/variants/rp2040/nibble_rp2040/variant.h +++ b/variants/rp2040/nibble_rp2040/variant.h @@ -2,7 +2,7 @@ #define BUTTON_PIN -1 // Pin 17 used for antenna switching via DIO4 -#define LED_PIN 1 +#define LED_POWER 1 #define HAS_CPU_SHUTDOWN 1 diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index cf49ff491..4dfad060e 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -10,7 +10,7 @@ #define I2C_SDA1 2 #define I2C_SCL1 3 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #define BUTTON_PIN 9 diff --git a/variants/rp2040/rp2040-lora/variant.h b/variants/rp2040/rp2040-lora/variant.h index 92b067457..51a760e0b 100644 --- a/variants/rp2040/rp2040-lora/variant.h +++ b/variants/rp2040/rp2040-lora/variant.h @@ -17,7 +17,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN -1 // Pin 17 used for antenna switching via DIO4 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED // #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/rpipico-slowclock/variant.h b/variants/rp2040/rpipico-slowclock/variant.h index fb97ec0fb..40d20e17a 100644 --- a/variants/rp2040/rpipico-slowclock/variant.h +++ b/variants/rp2040/rpipico-slowclock/variant.h @@ -52,7 +52,7 @@ #define BUTTON_PIN 18 #define EXT_NOTIFY_OUT 22 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/rpipico/variant.h b/variants/rp2040/rpipico/variant.h index 7efaeaf7a..dd849c290 100644 --- a/variants/rp2040/rpipico/variant.h +++ b/variants/rp2040/rpipico/variant.h @@ -15,7 +15,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/rpipicow/variant.h b/variants/rp2040/rpipicow/variant.h index fe94e615d..2de00545e 100644 --- a/variants/rp2040/rpipicow/variant.h +++ b/variants/rp2040/rpipicow/variant.h @@ -19,7 +19,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/senselora_rp2040/variant.h b/variants/rp2040/senselora_rp2040/variant.h index 04e21e073..f79ed66ca 100644 --- a/variants/rp2040/senselora_rp2040/variant.h +++ b/variants/rp2040/senselora_rp2040/variant.h @@ -5,7 +5,7 @@ #define BUTTON_PIN 2 #define BUTTON_NEED_PULLUP -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #undef BATTERY_PIN diff --git a/variants/rp2350/rpipico2/variant.h b/variants/rp2350/rpipico2/variant.h index 7efaeaf7a..dd849c290 100644 --- a/variants/rp2350/rpipico2/variant.h +++ b/variants/rp2350/rpipico2/variant.h @@ -15,7 +15,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index 2cf355a61..686326137 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -15,8 +15,8 @@ Do not expect a working Meshtastic device with this target. #define USE_STM32WLx -#define LED_PIN PB4 // LED1 -// #define LED_PIN PB3 // LED2 +#define LED_POWER PB4 // LED1 +// #define LED_POWER PB3 // LED2 #define LED_STATE_ON 1 #define SERIAL_PRINT_PORT 1 diff --git a/variants/stm32/milesight_gs301/variant.h b/variants/stm32/milesight_gs301/variant.h index e86e93fc4..f68d70458 100644 --- a/variants/stm32/milesight_gs301/variant.h +++ b/variants/stm32/milesight_gs301/variant.h @@ -6,7 +6,7 @@ // I/O #define LED_STATE_ON 1 #define PIN_LED1 PA0 // Green LED -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define PIN_LED2 PA0 // Red LED #define USER_LED PIN_LED2 #define BUTTON_PIN PC13 diff --git a/variants/stm32/rak3172/variant.h b/variants/stm32/rak3172/variant.h index b3f6cbcda..bd6decd4c 100644 --- a/variants/stm32/rak3172/variant.h +++ b/variants/stm32/rak3172/variant.h @@ -13,7 +13,7 @@ Do not expect a working Meshtastic device with this target. #define USE_STM32WLx -#define LED_PIN PA0 // Green LED +#define LED_POWER PA0 // Green LED #define LED_STATE_ON 1 #define RAK3172 diff --git a/variants/stm32/russell/variant.h b/variants/stm32/russell/variant.h index 796302d34..8773d5d8d 100644 --- a/variants/stm32/russell/variant.h +++ b/variants/stm32/russell/variant.h @@ -4,7 +4,7 @@ #define USE_STM32WLx // I/O -#define LED_PIN PA0 // Red LED +#define LED_POWER PA0 // Red LED #define LED_STATE_ON 1 #define BUTTON_PIN PH3 // Shared with BOOT0 #define BUTTON_NEED_PULLUP diff --git a/variants/stm32/wio-e5/variant.h b/variants/stm32/wio-e5/variant.h index 2b20eb2a6..da2c623fb 100644 --- a/variants/stm32/wio-e5/variant.h +++ b/variants/stm32/wio-e5/variant.h @@ -14,7 +14,7 @@ Do not expect a working Meshtastic device with this target. #define USE_STM32WLx -#define LED_PIN PB5 +#define LED_POWER PB5 #define LED_STATE_ON 0 #define WIO_E5 From 11bb2ee84eab7c0bcae421da7f8c4a1303d7a0cf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 5 Feb 2026 05:53:48 -0600 Subject: [PATCH 093/223] Upgrade trunk (#9368) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 9d563a39a..42b21b0e8 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,18 +8,18 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.497 - - renovate@42.84.2 - - prettier@3.8.0 - - trufflehog@3.92.5 + - checkov@3.2.500 + - renovate@43.3.4 + - prettier@3.8.1 + - trufflehog@3.93.0 - yamllint@1.38.0 - bandit@1.9.3 - - trivy@0.68.2 + - trivy@0.69.0 - taplo@0.10.0 - - ruff@0.14.13 + - ruff@0.15.0 - isort@7.0.0 - markdownlint@0.47.0 - - oxipng@10.0.0 + - oxipng@10.1.0 - svgo@4.0.0 - actionlint@1.7.10 - flake8@7.3.0 From f73d18384d6b133ab9862702aaaa9ba57f28c0e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 6 Feb 2026 06:22:34 -0600 Subject: [PATCH 094/223] Upgrade trunk (#9547) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 42b21b0e8..36154affa 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,12 +9,12 @@ plugins: lint: enabled: - checkov@3.2.500 - - renovate@43.3.4 + - renovate@43.4.0 - prettier@3.8.1 - trufflehog@3.93.0 - yamllint@1.38.0 - bandit@1.9.3 - - trivy@0.69.0 + - trivy@0.69.1 - taplo@0.10.0 - ruff@0.15.0 - isort@7.0.0 From 779e446d14455f0e6909db927e709fcc9059ae6b Mon Sep 17 00:00:00 2001 From: Colby Dillion Date: Fri, 6 Feb 2026 13:00:08 -0600 Subject: [PATCH 095/223] Fix hop_limit upgrade detection (#9550) Co-authored-by: Ben Meadors --- src/mesh/PacketHistory.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index b4af707ae..34393d259 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -90,9 +90,9 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd bool seenRecently = (found != NULL); // If found -> the packet was seen recently // Check for hop_limit upgrade scenario - if (seenRecently && wasUpgraded && found->hop_limit < p->hop_limit) { - LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit, - p->hop_limit); + if (seenRecently && wasUpgraded && getHighestHopLimit(*found) < p->hop_limit) { + LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, + getHighestHopLimit(*found), p->hop_limit); *wasUpgraded = true; } else if (wasUpgraded) { *wasUpgraded = false; // Initialize to false if not an upgrade From 4a4b1f4a878b51b6764bc6aa4be188bb8c4088d8 Mon Sep 17 00:00:00 2001 From: Austin Date: Fri, 6 Feb 2026 20:36:21 -0500 Subject: [PATCH 096/223] meshtasticd: Fix install on Fedora 43 (#9556) --- meshtasticd.spec.rpkg | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index fc14ede7f..ec2c15141 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -59,8 +59,14 @@ BuildRequires: pkgconfig(libbsd-overlay) Requires: systemd-udev +# Declare that this package provides the user/group it creates in %pre +# Required for Fedora 43+ which tracks users/groups as RPM dependencies +Provides: user(%{meshtasticd_user}) +Provides: group(%{meshtasticd_user}) +Provides: group(spi) + %description -Meshtastic daemon for controlling Meshtastic devices. Meshtastic is an off-grid +Meshtastic daemon. Meshtastic is an off-grid text communication platform that uses inexpensive LoRa radios. %prep From e2cf401ad395d9d0b241f1f4dc38b78bd69889d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 06:16:30 -0600 Subject: [PATCH 097/223] Update meshtastic/device-ui digest to 6c75195 (#9553) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 777b02b7b..1bdaf0a2d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,7 +120,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/63967a4a557d33d56fc5746f9128200dde2d88c5.zip + https://github.com/meshtastic/device-ui/archive/6c75195e9987b7a49563232234f2f868dd343cae.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From ba016fd91a3e6f80f1920ea98fc838671c2f0be9 Mon Sep 17 00:00:00 2001 From: Colby Dillion Date: Fri, 6 Feb 2026 13:00:08 -0600 Subject: [PATCH 098/223] Fix hop_limit upgrade detection (#9550) Co-authored-by: Ben Meadors --- src/mesh/PacketHistory.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index b4af707ae..34393d259 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -90,9 +90,9 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd bool seenRecently = (found != NULL); // If found -> the packet was seen recently // Check for hop_limit upgrade scenario - if (seenRecently && wasUpgraded && found->hop_limit < p->hop_limit) { - LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit, - p->hop_limit); + if (seenRecently && wasUpgraded && getHighestHopLimit(*found) < p->hop_limit) { + LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, + getHighestHopLimit(*found), p->hop_limit); *wasUpgraded = true; } else if (wasUpgraded) { *wasUpgraded = false; // Initialize to false if not an upgrade From 5280caf9d8c819622b51090ea871effbe8e13cd8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 7 Feb 2026 06:24:56 -0600 Subject: [PATCH 099/223] Update protobufs (#9559) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/config.pb.h | 12 ++++++++---- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index bc63a57f9..e80cb2e41 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit bc63a57f9e5dba8a7c90ee0bd4a9840862d61f6d +Subproject commit e80cb2e410d8054f5da67ba6767613c4336b6c88 diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index d93f6fafa..c669de6a8 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -505,6 +505,8 @@ typedef struct _meshtastic_Config_DisplayConfig { /* If false (default), the device will use short names for various display screens. If true, node names will show in long format */ bool use_long_node_name; + /* If true, the device will display message bubbles on screen. */ + bool enable_message_bubbles; } meshtastic_Config_DisplayConfig; /* Lora Config */ @@ -732,7 +734,7 @@ extern "C" { #define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0} -#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0} +#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0, 0} #define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} @@ -743,7 +745,7 @@ extern "C" { #define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0} #define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0} -#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0} +#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0, 0} #define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0} #define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0} #define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0} @@ -811,6 +813,7 @@ extern "C" { #define meshtastic_Config_DisplayConfig_compass_orientation_tag 11 #define meshtastic_Config_DisplayConfig_use_12h_clock_tag 12 #define meshtastic_Config_DisplayConfig_use_long_node_name_tag 13 +#define meshtastic_Config_DisplayConfig_enable_message_bubbles_tag 14 #define meshtastic_Config_LoRaConfig_use_preset_tag 1 #define meshtastic_Config_LoRaConfig_modem_preset_tag 2 #define meshtastic_Config_LoRaConfig_bandwidth_tag 3 @@ -957,7 +960,8 @@ X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \ X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \ X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11) \ X(a, STATIC, SINGULAR, BOOL, use_12h_clock, 12) \ -X(a, STATIC, SINGULAR, BOOL, use_long_node_name, 13) +X(a, STATIC, SINGULAR, BOOL, use_long_node_name, 13) \ +X(a, STATIC, SINGULAR, BOOL, enable_message_bubbles, 14) #define meshtastic_Config_DisplayConfig_CALLBACK NULL #define meshtastic_Config_DisplayConfig_DEFAULT NULL @@ -1035,7 +1039,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg; #define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size #define meshtastic_Config_BluetoothConfig_size 10 #define meshtastic_Config_DeviceConfig_size 100 -#define meshtastic_Config_DisplayConfig_size 34 +#define meshtastic_Config_DisplayConfig_size 36 #define meshtastic_Config_LoRaConfig_size 85 #define meshtastic_Config_NetworkConfig_IpV4Config_size 20 #define meshtastic_Config_NetworkConfig_size 204 diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 57e7df8fc..2aaa12da5 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2362 +#define meshtastic_BackupPreferences_size 2364 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index f11b13419..b2b71733c 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -193,7 +193,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size -#define meshtastic_LocalConfig_size 749 +#define meshtastic_LocalConfig_size 751 #define meshtastic_LocalModuleConfig_size 758 #ifdef __cplusplus From 39139cc2ea2dde5ba56095e2a333ba26afab60d8 Mon Sep 17 00:00:00 2001 From: Austin Date: Sat, 7 Feb 2026 11:13:01 -0500 Subject: [PATCH 100/223] RPM: Include meshtasticd-start.sh (#9561) --- meshtasticd.spec.rpkg | 1 + 1 file changed, 1 insertion(+) diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index ec2c15141..ceb954b21 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -157,6 +157,7 @@ fi %license LICENSE %doc README.md %{_bindir}/meshtasticd +%{_bindir}/meshtasticd-start.sh %dir %{_localstatedir}/lib/meshtasticd %{_udevrulesdir}/99-meshtasticd-udev.rules %dir %{_sysconfdir}/meshtasticd From 53231ae4b16271de4af7027a4a47d434a757f438 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 7 Feb 2026 15:41:31 -0600 Subject: [PATCH 101/223] Added toggable config and default for larger screens to enable / hide bubbles on chat messages (#9560) * Added toggable config and default for largeer screens to enable / hide bubbles on chat messages * Refactor message bubble rendering logic for improved layout and consistency * Move osk_found initialization for trackball/encoder devices before module setup to fix missing keyboard for L1 * Utilize current checks for consistency * Reverted last changes --------- Co-authored-by: Jason P Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- src/graphics/draw/MenuHandler.cpp | 300 ++++++++++++++------------ src/graphics/draw/MenuHandler.h | 110 +++++----- src/graphics/draw/MessageRenderer.cpp | 192 ++++++++--------- src/main.cpp | 13 +- src/mesh/NodeDB.cpp | 4 + src/modules/KeyVerificationModule.cpp | 4 +- 6 files changed, 330 insertions(+), 293 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 9fe018d81..195da09f9 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -58,7 +58,7 @@ BannerOverlayOptions createStaticBannerOptions(const char *message, const MenuOp } // namespace -menuHandler::screenMenus menuHandler::menuQueue = menu_none; +menuHandler::screenMenus menuHandler::menuQueue = MenuNone; uint32_t menuHandler::pickedNodeNum = 0; bool test_enabled = false; uint8_t test_count = 0; @@ -66,7 +66,7 @@ uint8_t test_count = 0; void menuHandler::loraMenu() { static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"}; - enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, frequency_slot = 3, lora_picker = 4 }; + enum optionsNumbers { Back = 0, DeviceRolePicker = 1, RadioPresetPicker = 2, FrequencySlot = 3, LoraPicker = 4 }; BannerOverlayOptions bannerOptions; bannerOptions.message = "LoRa Actions"; bannerOptions.optionsArrayPtr = optionsArray; @@ -74,14 +74,14 @@ void menuHandler::loraMenu() bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { // No action - } else if (selected == device_role_picker) { - menuHandler::menuQueue = menuHandler::device_role_picker; - } else if (selected == radio_preset_picker) { - menuHandler::menuQueue = menuHandler::radio_preset_picker; - } else if (selected == frequency_slot) { - menuHandler::menuQueue = menuHandler::frequency_slot; - } else if (selected == lora_picker) { - menuHandler::menuQueue = menuHandler::lora_picker; + } else if (selected == DeviceRolePicker) { + menuHandler::menuQueue = menuHandler::DeviceRolePicker; + } else if (selected == RadioPresetPicker) { + menuHandler::menuQueue = menuHandler::RadioPresetPicker; + } else if (selected == FrequencySlot) { + menuHandler::menuQueue = menuHandler::FrequencySlot; + } else if (selected == LoraPicker) { + menuHandler::menuQueue = menuHandler::LoraPicker; } }; screen->showOverlayBanner(bannerOptions); @@ -102,7 +102,7 @@ void menuHandler::OnboardMessage() bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 2; bannerOptions.bannerCallback = [](int selected) -> void { - menuHandler::menuQueue = menuHandler::no_timeout_lora_picker; + menuHandler::menuQueue = menuHandler::NoTimeoutLoraPicker; screen->runNow(); }; screen->showOverlayBanner(bannerOptions); @@ -216,7 +216,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration) screen->showOverlayBanner(bannerOptions); } -void menuHandler::DeviceRolePicker() +void menuHandler::deviceRolePicker() { static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"}; enum optionsNumbers { @@ -232,7 +232,7 @@ void menuHandler::DeviceRolePicker() bannerOptions.optionsCount = 5; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { - menuHandler::menuQueue = menuHandler::lora_Menu; + menuHandler::menuQueue = menuHandler::LoraMenu; screen->runNow(); return; } else if (selected == devicerole_client) { @@ -300,7 +300,7 @@ void menuHandler::FrequencySlotPicker() bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { - menuHandler::menuQueue = menuHandler::lora_Menu; + menuHandler::menuQueue = menuHandler::LoraMenu; screen->runNow(); return; } @@ -313,7 +313,7 @@ void menuHandler::FrequencySlotPicker() screen->showOverlayBanner(bannerOptions); } -void menuHandler::RadioPresetPicker() +void menuHandler::radioPresetPicker() { static const RadioPresetOption presetOptions[] = { {"Back", OptionsAction::Back}, @@ -333,7 +333,7 @@ void menuHandler::RadioPresetPicker() auto bannerOptions = createStaticBannerOptions("Radio Preset", presetOptions, presetLabels, [](const RadioPresetOption &option, int) -> void { if (option.action == OptionsAction::Back) { - menuHandler::menuQueue = menuHandler::lora_Menu; + menuHandler::menuQueue = menuHandler::LoraMenu; screen->runNow(); return; } @@ -352,7 +352,7 @@ void menuHandler::RadioPresetPicker() screen->showOverlayBanner(bannerOptions); } -void menuHandler::TwelveHourPicker() +void menuHandler::twelveHourPicker() { static const char *optionsArray[] = {"Back", "12-hour", "24-hour"}; enum optionsNumbers { Back = 0, twelve = 1, twentyfour = 2 }; @@ -362,7 +362,7 @@ void menuHandler::TwelveHourPicker() bannerOptions.optionsCount = 3; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { - menuHandler::menuQueue = menuHandler::clock_menu; + menuHandler::menuQueue = menuHandler::ClockMenu; screen->runNow(); } else if (selected == twelve) { config.display.use_12h_clock = true; @@ -390,7 +390,7 @@ void menuHandler::showConfirmationBanner(const char *message, std::functionshowOverlayBanner(confirmBanner); } -void menuHandler::ClockFacePicker() +void menuHandler::clockFacePicker() { static const ClockFaceOption clockFaceOptions[] = { {"Back", OptionsAction::Back}, @@ -404,7 +404,7 @@ void menuHandler::ClockFacePicker() auto bannerOptions = createStaticBannerOptions("Which Face?", clockFaceOptions, clockFaceLabels, [](const ClockFaceOption &option, int) -> void { if (option.action == OptionsAction::Back) { - menuHandler::menuQueue = menuHandler::clock_menu; + menuHandler::menuQueue = menuHandler::ClockMenu; screen->runNow(); return; } @@ -456,7 +456,7 @@ void menuHandler::TZPicker() auto bannerOptions = createStaticBannerOptions( "Pick Timezone", timezoneOptions, timezoneLabels, [](const TimezoneOption &option, int) -> void { if (option.action == OptionsAction::Back) { - menuHandler::menuQueue = menuHandler::clock_menu; + menuHandler::menuQueue = menuHandler::ClockMenu; screen->runNow(); return; } @@ -503,13 +503,13 @@ void menuHandler::clockMenu() bannerOptions.optionsCount = 4; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Clock) { - menuHandler::menuQueue = menuHandler::clock_face_picker; + menuHandler::menuQueue = menuHandler::ClockFacePicker; screen->runNow(); } else if (selected == Time) { - menuHandler::menuQueue = menuHandler::twelve_hour_picker; + menuHandler::menuQueue = menuHandler::TwelveHourPicker; screen->runNow(); } else if (selected == Timezone) { - menuHandler::menuQueue = menuHandler::TZ_picker; + menuHandler::menuQueue = menuHandler::TzPicker; screen->runNow(); } }; @@ -572,12 +572,12 @@ void menuHandler::messageResponseMenu() LOG_DEBUG("[ReplyCtx] mode=%d ch=%d peer=0x%08x", (int)mode, ch, (unsigned int)peer); if (selected == ViewMode) { - menuHandler::menuQueue = menuHandler::message_viewmode_menu; + menuHandler::menuQueue = menuHandler::MessageViewModeMenu; screen->runNow(); // Reply submenu } else if (selected == ReplyMenu) { - menuHandler::menuQueue = menuHandler::reply_menu; + menuHandler::menuQueue = menuHandler::ReplyMenu; screen->runNow(); } else if (selected == MuteChannel) { @@ -589,7 +589,7 @@ void menuHandler::messageResponseMenu() } } else if (selected == DeleteMenu) { - menuHandler::menuQueue = menuHandler::delete_messages_menu; + menuHandler::menuQueue = menuHandler::DeleteMessagesMenu; screen->runNow(); #ifdef HAS_I2S @@ -649,7 +649,7 @@ void menuHandler::replyMenu() uint32_t peer = graphics::MessageRenderer::getThreadPeer(); if (selected == Back) { - menuHandler::menuQueue = menuHandler::message_response_menu; + menuHandler::menuQueue = menuHandler::MessageResponseMenu; screen->runNow(); return; } @@ -737,7 +737,7 @@ void menuHandler::deleteMessagesMenu() uint32_t peer = graphics::MessageRenderer::getThreadPeer(); if (selected == Back) { - menuHandler::menuQueue = menuHandler::message_response_menu; + menuHandler::menuQueue = menuHandler::MessageResponseMenu; screen->runNow(); return; } @@ -901,7 +901,7 @@ void menuHandler::messageViewModeMenu() bannerOptions.bannerCallback = [=](int selected) -> void { LOG_DEBUG("messageViewModeMenu: selected=%d", selected); if (selected == -1) { - menuHandler::menuQueue = menuHandler::message_response_menu; + menuHandler::menuQueue = menuHandler::MessageResponseMenu; screen->runNow(); } else if (selected == -2) { graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::ALL); @@ -1083,23 +1083,23 @@ void menuHandler::systemBaseMenu() bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Notifications) { - menuHandler::menuQueue = menuHandler::buzzermodemenupicker; + menuHandler::menuQueue = menuHandler::BuzzerModeMenuPicker; screen->runNow(); } else if (selected == ScreenOptions) { - menuHandler::menuQueue = menuHandler::screen_options_menu; + menuHandler::menuQueue = menuHandler::ScreenOptionsMenu; screen->runNow(); } else if (selected == PowerMenu) { - menuHandler::menuQueue = menuHandler::power_menu; + menuHandler::menuQueue = menuHandler::PowerMenu; screen->runNow(); } else if (selected == Test) { - menuHandler::menuQueue = menuHandler::test_menu; + menuHandler::menuQueue = menuHandler::TestMenu; screen->runNow(); } else if (selected == Bluetooth) { - menuQueue = bluetooth_toggle_menu; + menuQueue = BluetoothToggleMenu; screen->runNow(); #if HAS_WIFI && !defined(ARCH_PORTDUINO) } else if (selected == WiFiToggle) { - menuQueue = wifi_toggle_menu; + menuQueue = WifiToggleMenu; screen->runNow(); #endif } else if (selected == Back && !test_enabled) { @@ -1177,7 +1177,7 @@ void menuHandler::favoriteBaseMenu() evt.action = UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE; screen->handleUIFrameEvent(&evt); } else if (selected == Remove) { - menuHandler::menuQueue = menuHandler::remove_favorite; + menuHandler::menuQueue = menuHandler::RemoveFavorite; screen->runNow(); } else if (selected == TraceRoute) { if (traceRouteModule) { @@ -1238,15 +1238,15 @@ void menuHandler::positionBaseMenu() auto action = static_cast(option.value); switch (action) { case PositionAction::GpsToggle: - menuQueue = gps_toggle_menu; + menuQueue = GpsToggleMenu; screen->runNow(); break; case PositionAction::GpsFormat: - menuQueue = gps_format_menu; + menuQueue = GpsFormatMenu; screen->runNow(); break; case PositionAction::CompassMenu: - menuQueue = compass_point_north_menu; + menuQueue = CompassPointNorthMenu; screen->runNow(); break; case PositionAction::CompassCalibrate: @@ -1255,15 +1255,15 @@ void menuHandler::positionBaseMenu() } break; case PositionAction::GPSSmartPosition: - menuQueue = gps_smart_position_menu; + menuQueue = GpsSmartPositionMenu; screen->runNow(); break; case PositionAction::GPSUpdateInterval: - menuQueue = gps_update_interval_menu; + menuQueue = GpsUpdateIntervalMenu; screen->runNow(); break; case PositionAction::GPSPositionBroadcast: - menuQueue = gps_position_broadcast_menu; + menuQueue = GpsPositionBroadcastMenu; screen->runNow(); break; } @@ -1303,13 +1303,13 @@ void menuHandler::nodeListMenu() bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == NodePicker) { - menuQueue = NodePicker_menu; + menuQueue = NodePickerMenu; screen->runNow(); } else if (selected == Reset) { - menuQueue = reset_node_db_menu; + menuQueue = ResetNodeDbMenu; screen->runNow(); } else if (selected == NodeNameLength) { - menuHandler::menuQueue = menuHandler::node_name_length_menu; + menuHandler::menuQueue = menuHandler::NodeNameLengthMenu; screen->runNow(); } }; @@ -1330,12 +1330,12 @@ void menuHandler::NodePicker() menuHandler::pickedNodeNum = nodenum; // Keep UI favorite context in sync (used elsewhere for some node-based actions) graphics::UIRenderer::currentFavoriteNodeNum = nodenum; - menuQueue = Manage_Node_menu; + menuQueue = ManageNodeMenu; screen->runNow(); }); } -void menuHandler::ManageNodeMenu() +void menuHandler::manageNodeMenu() { // If we don't have a node selected yet, go fast exit auto node = nodeDB->getMeshNode(menuHandler::pickedNodeNum); @@ -1391,7 +1391,7 @@ void menuHandler::ManageNodeMenu() bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { - menuQueue = node_base_menu; + menuQueue = NodeBaseMenu; screen->runNow(); return; } @@ -1483,7 +1483,7 @@ void menuHandler::nodeNameLengthMenu() auto bannerOptions = createStaticBannerOptions("Node Name Length", nodeNameOptions, nodeNameLabels, [](const NodeNameOption &option, int) -> void { if (option.action == OptionsAction::Back) { - menuQueue = node_base_menu; + menuQueue = NodeBaseMenu; screen->runNow(); return; } @@ -1529,7 +1529,7 @@ void menuHandler::resetNodeDBMenu() nodeDB->resetNodes(1); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); } else if (selected == 0) { - menuQueue = node_base_menu; + menuQueue = NodeBaseMenu; screen->runNow(); } }; @@ -1551,7 +1551,7 @@ void menuHandler::compassNorthMenu() auto bannerOptions = createStaticBannerOptions("North Directions?", compassOptions, compassLabels, [](const CompassOption &option, int) -> void { if (option.action == OptionsAction::Back) { - menuQueue = position_base_menu; + menuQueue = PositionBaseMenu; screen->runNow(); return; } @@ -1596,7 +1596,7 @@ void menuHandler::GPSToggleMenu() auto bannerOptions = createStaticBannerOptions("Toggle GPS", gpsToggleOptions, toggleLabels, [](const GPSToggleOption &option, int) -> void { if (option.action == OptionsAction::Back) { - menuQueue = position_base_menu; + menuQueue = PositionBaseMenu; screen->runNow(); return; } @@ -1661,7 +1661,7 @@ void menuHandler::GPSFormatMenu() auto onSelection = [](const GPSFormatOption &option, int) -> void { if (option.action == OptionsAction::Back) { - menuQueue = position_base_menu; + menuQueue = PositionBaseMenu; screen->runNow(); return; } @@ -1716,7 +1716,7 @@ void menuHandler::GPSSmartPositionMenu() bannerOptions.optionsCount = 3; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == 0) { - menuQueue = position_base_menu; + menuQueue = PositionBaseMenu; screen->runNow(); } else if (selected == 1) { config.position.position_broadcast_smart_enabled = true; @@ -1745,7 +1745,7 @@ void menuHandler::GPSUpdateIntervalMenu() bannerOptions.optionsCount = 16; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == 0) { - menuQueue = position_base_menu; + menuQueue = PositionBaseMenu; screen->runNow(); } else if (selected == 1) { config.position.gps_update_interval = 8; @@ -1833,7 +1833,7 @@ void menuHandler::GPSPositionBroadcastMenu() bannerOptions.optionsCount = 17; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == 0) { - menuQueue = position_base_menu; + menuQueue = PositionBaseMenu; screen->runNow(); } else if (selected == 1) { config.position.position_broadcast_secs = 60; @@ -1916,7 +1916,7 @@ void menuHandler::GPSPositionBroadcastMenu() #endif -void menuHandler::BluetoothToggleMenu() +void menuHandler::bluetoothToggleMenu() { static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; BannerOverlayOptions bannerOptions; @@ -2044,7 +2044,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display) auto bannerOptions = createStaticBannerOptions( "Select Screen Color", colorOptions, colorLabels, [display](const ScreenColorOption &option, int) -> void { if (option.action == OptionsAction::Back) { - menuQueue = system_base_menu; + menuQueue = SystemBaseMenu; screen->runNow(); return; } @@ -2139,7 +2139,7 @@ void menuHandler::rebootMenu() messageStore.saveToFlash(); rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; } else { - menuQueue = power_menu; + menuQueue = PowerMenu; screen->runNow(); } }; @@ -2161,7 +2161,7 @@ void menuHandler::shutdownMenu() InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); } else { - menuQueue = power_menu; + menuQueue = PowerMenu; screen->runNow(); } }; @@ -2222,14 +2222,14 @@ void menuHandler::testMenu() bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == NumberPicker) { - menuQueue = number_test; + menuQueue = NumberTest; screen->runNow(); } else if (selected == ShowChirpy) { screen->toggleFrameVisibility("chirpy"); screen->setFrames(Screen::FOCUS_SYSTEM); } else { - menuQueue = system_base_menu; + menuQueue = SystemBaseMenu; screen->runNow(); } }; @@ -2253,7 +2253,7 @@ void menuHandler::wifiBaseMenu() bannerOptions.optionsCount = 2; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Wifi_toggle) { - menuQueue = wifi_toggle_menu; + menuQueue = WifiToggleMenu; screen->runNow(); } }; @@ -2302,9 +2302,9 @@ void menuHandler::screenOptionsMenu() hasSupportBrightness = false; #endif - enum optionsNumbers { Back, Brightness, ScreenColor, FrameToggles, DisplayUnits }; - static const char *optionsArray[5] = {"Back"}; - static int optionsEnumArray[5] = {Back}; + enum optionsNumbers { Back, Brightness, ScreenColor, FrameToggles, DisplayUnits, MessageBubbles }; + static const char *optionsArray[6] = {"Back"}; + static int optionsEnumArray[6] = {Back}; int options = 1; // Only show brightness for B&W displays @@ -2326,6 +2326,9 @@ void menuHandler::screenOptionsMenu() optionsArray[options] = "Display Units"; optionsEnumArray[options++] = DisplayUnits; + optionsArray[options] = "Message Bubbles"; + optionsEnumArray[options++] = MessageBubbles; + BannerOverlayOptions bannerOptions; bannerOptions.message = "Display Options"; bannerOptions.optionsArrayPtr = optionsArray; @@ -2333,10 +2336,10 @@ void menuHandler::screenOptionsMenu() bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Brightness) { - menuHandler::menuQueue = menuHandler::brightness_picker; + menuHandler::menuQueue = menuHandler::BrightnessPicker; screen->runNow(); } else if (selected == ScreenColor) { - menuHandler::menuQueue = menuHandler::tftcolormenupicker; + menuHandler::menuQueue = menuHandler::TftColorMenuPicker; screen->runNow(); } else if (selected == FrameToggles) { menuHandler::menuQueue = menuHandler::FrameToggles; @@ -2344,8 +2347,11 @@ void menuHandler::screenOptionsMenu() } else if (selected == DisplayUnits) { menuHandler::menuQueue = menuHandler::DisplayUnits; screen->runNow(); + } else if (selected == MessageBubbles) { + menuHandler::menuQueue = menuHandler::MessageBubblesMenu; + screen->runNow(); } else { - menuQueue = system_base_menu; + menuQueue = SystemBaseMenu; screen->runNow(); } }; @@ -2381,16 +2387,16 @@ void menuHandler::powerMenu() bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Reboot) { - menuHandler::menuQueue = menuHandler::reboot_menu; + menuHandler::menuQueue = menuHandler::RebootMenu; screen->runNow(); } else if (selected == Shutdown) { - menuHandler::menuQueue = menuHandler::shutdown_menu; + menuHandler::menuQueue = menuHandler::ShutdownMenu; screen->runNow(); } else if (selected == MUI) { - menuHandler::menuQueue = menuHandler::mui_picker; + menuHandler::menuQueue = menuHandler::MuiPicker; screen->runNow(); } else { - menuQueue = system_base_menu; + menuQueue = SystemBaseMenu; screen->runNow(); } }; @@ -2428,7 +2434,7 @@ void menuHandler::keyVerificationFinalPrompt() } } -void menuHandler::FrameToggles_menu() +void menuHandler::frameTogglesMenu() { enum optionsNumbers { Finish, @@ -2572,7 +2578,7 @@ void menuHandler::FrameToggles_menu() screen->showOverlayBanner(bannerOptions); } -void menuHandler::DisplayUnits_menu() +void menuHandler::displayUnitsMenu() { enum optionsNumbers { Back, MetricUnits, ImperialUnits }; @@ -2593,7 +2599,34 @@ void menuHandler::DisplayUnits_menu() config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL; service->reloadConfig(SEGMENT_CONFIG); } else { - menuHandler::menuQueue = menuHandler::screen_options_menu; + menuHandler::menuQueue = menuHandler::ScreenOptionsMenu; + screen->runNow(); + } + }; + screen->showOverlayBanner(bannerOptions); +} + +void menuHandler::messageBubblesMenu() +{ + enum optionsNumbers { Back, ShowBubbles, HideBubbles }; + + static const char *optionsArray[] = {"Back", "Show Bubbles", "Hide Bubbles"}; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Message Bubbles"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 3; + bannerOptions.InitialSelected = config.display.enable_message_bubbles ? 1 : 2; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == ShowBubbles) { + config.display.enable_message_bubbles = true; + service->reloadConfig(SEGMENT_CONFIG); + LOG_INFO("Message bubbles enabled"); + } else if (selected == HideBubbles) { + config.display.enable_message_bubbles = false; + service->reloadConfig(SEGMENT_CONFIG); + LOG_INFO("Message bubbles disabled"); + } else { + menuHandler::menuQueue = menuHandler::ScreenOptionsMenu; screen->runNow(); } }; @@ -2602,153 +2635,156 @@ void menuHandler::DisplayUnits_menu() void menuHandler::handleMenuSwitch(OLEDDisplay *display) { - if (menuQueue != menu_none) + if (menuQueue != MenuNone) test_count = 0; switch (menuQueue) { - case menu_none: + case MenuNone: break; - case lora_Menu: + case LoraMenu: loraMenu(); break; - case lora_picker: + case LoraPicker: LoraRegionPicker(); break; - case device_role_picker: - DeviceRolePicker(); + case DeviceRolePicker: + deviceRolePicker(); break; - case radio_preset_picker: - RadioPresetPicker(); + case RadioPresetPicker: + radioPresetPicker(); break; - case frequency_slot: + case FrequencySlot: FrequencySlotPicker(); break; - case no_timeout_lora_picker: + case NoTimeoutLoraPicker: LoraRegionPicker(0); break; - case TZ_picker: + case TzPicker: TZPicker(); break; - case twelve_hour_picker: - TwelveHourPicker(); + case TwelveHourPicker: + twelveHourPicker(); break; - case clock_face_picker: - ClockFacePicker(); + case ClockFacePicker: + clockFacePicker(); break; - case clock_menu: + case ClockMenu: clockMenu(); break; - case system_base_menu: + case SystemBaseMenu: systemBaseMenu(); break; - case position_base_menu: + case PositionBaseMenu: positionBaseMenu(); break; - case node_base_menu: + case NodeBaseMenu: nodeListMenu(); break; #if !MESHTASTIC_EXCLUDE_GPS - case gps_toggle_menu: + case GpsToggleMenu: GPSToggleMenu(); break; - case gps_format_menu: + case GpsFormatMenu: GPSFormatMenu(); break; - case gps_smart_position_menu: + case GpsSmartPositionMenu: GPSSmartPositionMenu(); break; - case gps_update_interval_menu: + case GpsUpdateIntervalMenu: GPSUpdateIntervalMenu(); break; - case gps_position_broadcast_menu: + case GpsPositionBroadcastMenu: GPSPositionBroadcastMenu(); break; #endif - case compass_point_north_menu: + case CompassPointNorthMenu: compassNorthMenu(); break; - case reset_node_db_menu: + case ResetNodeDbMenu: resetNodeDBMenu(); break; - case buzzermodemenupicker: + case BuzzerModeMenuPicker: BuzzerModeMenu(); break; - case mui_picker: + case MuiPicker: switchToMUIMenu(); break; - case tftcolormenupicker: + case TftColorMenuPicker: TFTColorPickerMenu(display); break; - case brightness_picker: + case BrightnessPicker: BrightnessPickerMenu(); break; - case node_name_length_menu: + case NodeNameLengthMenu: nodeNameLengthMenu(); break; - case reboot_menu: + case RebootMenu: rebootMenu(); break; - case shutdown_menu: + case ShutdownMenu: shutdownMenu(); break; - case NodePicker_menu: + case NodePickerMenu: NodePicker(); break; - case Manage_Node_menu: - ManageNodeMenu(); + case ManageNodeMenu: + manageNodeMenu(); break; - case remove_favorite: + case RemoveFavorite: removeFavoriteMenu(); break; - case trace_route_menu: + case TraceRouteMenu: traceRouteMenu(); break; - case test_menu: + case TestMenu: testMenu(); break; - case number_test: + case NumberTest: numberTest(); break; - case wifi_toggle_menu: + case WifiToggleMenu: wifiToggleMenu(); break; - case key_verification_init: + case KeyVerificationInit: keyVerificationInitMenu(); break; - case key_verification_final_prompt: + case KeyVerificationFinalPrompt: keyVerificationFinalPrompt(); break; - case bluetooth_toggle_menu: - BluetoothToggleMenu(); + case BluetoothToggleMenu: + bluetoothToggleMenu(); break; - case screen_options_menu: + case ScreenOptionsMenu: screenOptionsMenu(); break; - case power_menu: + case PowerMenu: powerMenu(); break; case FrameToggles: - FrameToggles_menu(); + frameTogglesMenu(); break; case DisplayUnits: - DisplayUnits_menu(); + displayUnitsMenu(); break; - case throttle_message: + case ThrottleMessage: screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000); break; - case message_response_menu: + case MessageResponseMenu: messageResponseMenu(); break; - case reply_menu: + case ReplyMenu: replyMenu(); break; - case delete_messages_menu: + case DeleteMessagesMenu: deleteMessagesMenu(); break; - case message_viewmode_menu: + case MessageViewModeMenu: messageViewModeMenu(); break; + case MessageBubblesMenu: + messageBubblesMenu(); + break; } - menuQueue = menu_none; + menuQueue = MenuNone; } void menuHandler::saveUIConfig() diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 1b964678b..4a0360412 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -8,53 +8,54 @@ class menuHandler { public: enum screenMenus { - menu_none, - lora_Menu, - lora_picker, - device_role_picker, - radio_preset_picker, - frequency_slot, - no_timeout_lora_picker, - TZ_picker, - twelve_hour_picker, - clock_face_picker, - clock_menu, - position_base_menu, - node_base_menu, - gps_toggle_menu, - gps_format_menu, - gps_smart_position_menu, - gps_update_interval_menu, - gps_position_broadcast_menu, - compass_point_north_menu, - reset_node_db_menu, - buzzermodemenupicker, - mui_picker, - tftcolormenupicker, - brightness_picker, - reboot_menu, - shutdown_menu, - NodePicker_menu, - Manage_Node_menu, - remove_favorite, - test_menu, - number_test, - wifi_toggle_menu, - bluetooth_toggle_menu, - screen_options_menu, - power_menu, - system_base_menu, - key_verification_init, - key_verification_final_prompt, - trace_route_menu, - throttle_message, - message_response_menu, - message_viewmode_menu, - reply_menu, - delete_messages_menu, - node_name_length_menu, + MenuNone, + LoraMenu, + LoraPicker, + DeviceRolePicker, + RadioPresetPicker, + FrequencySlot, + NoTimeoutLoraPicker, + TzPicker, + TwelveHourPicker, + ClockFacePicker, + ClockMenu, + PositionBaseMenu, + NodeBaseMenu, + GpsToggleMenu, + GpsFormatMenu, + GpsSmartPositionMenu, + GpsUpdateIntervalMenu, + GpsPositionBroadcastMenu, + CompassPointNorthMenu, + ResetNodeDbMenu, + BuzzerModeMenuPicker, + MuiPicker, + TftColorMenuPicker, + BrightnessPicker, + RebootMenu, + ShutdownMenu, + NodePickerMenu, + ManageNodeMenu, + RemoveFavorite, + TestMenu, + NumberTest, + WifiToggleMenu, + BluetoothToggleMenu, + ScreenOptionsMenu, + PowerMenu, + SystemBaseMenu, + KeyVerificationInit, + KeyVerificationFinalPrompt, + TraceRouteMenu, + ThrottleMessage, + MessageResponseMenu, + MessageViewModeMenu, + ReplyMenu, + DeleteMessagesMenu, + NodeNameLengthMenu, FrameToggles, - DisplayUnits + DisplayUnits, + MessageBubblesMenu }; static screenMenus menuQueue; static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu @@ -62,15 +63,15 @@ class menuHandler static void OnboardMessage(); static void LoraRegionPicker(uint32_t duration = 30000); static void loraMenu(); - static void DeviceRolePicker(); - static void RadioPresetPicker(); + static void deviceRolePicker(); + static void radioPresetPicker(); static void FrequencySlotPicker(); static void handleMenuSwitch(OLEDDisplay *display); static void showConfirmationBanner(const char *message, std::function onConfirm); static void clockMenu(); static void TZPicker(); - static void TwelveHourPicker(); - static void ClockFacePicker(); + static void twelveHourPicker(); + static void clockFacePicker(); static void messageResponseMenu(); static void messageViewModeMenu(); static void replyMenu(); @@ -95,7 +96,7 @@ class menuHandler static void rebootMenu(); static void shutdownMenu(); static void NodePicker(); - static void ManageNodeMenu(); + static void manageNodeMenu(); static void addFavoriteMenu(); static void removeFavoriteMenu(); static void traceRouteMenu(); @@ -106,15 +107,16 @@ class menuHandler static void screenOptionsMenu(); static void powerMenu(); static void nodeNameLengthMenu(); - static void FrameToggles_menu(); - static void DisplayUnits_menu(); + static void frameTogglesMenu(); + static void displayUnitsMenu(); + static void messageBubblesMenu(); static void textMessageMenu(); private: static void saveUIConfig(); static void keyVerificationInitMenu(); static void keyVerificationFinalPrompt(); - static void BluetoothToggleMenu(); + static void bluetoothToggleMenu(); }; /* Generic Menu Options designations */ diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 193164439..79d8b1ccd 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -527,8 +527,12 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 constexpr int BUBBLE_MIN_W = 24; constexpr int BUBBLE_TEXT_INDENT = 2; + // Check if bubbles are enabled + const bool showBubbles = config.display.enable_message_bubbles; + const int textIndent = showBubbles ? (BUBBLE_PAD_X + BUBBLE_TEXT_INDENT) : LEFT_MARGIN; + // Derived widths - const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2); + const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (showBubbles ? (BUBBLE_PAD_X * 2) : 0); const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH; // Title string depending on mode @@ -796,114 +800,105 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } } - // Draw bubbles - for (size_t bi = 0; bi < blocks.size(); ++bi) { - const auto &b = blocks[bi]; - if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end) - continue; + // Draw bubbles (only if enabled) + if (showBubbles) { + for (size_t bi = 0; bi < blocks.size(); ++bi) { + const auto &b = blocks[bi]; + if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end) + continue; - int visualTop = lineTop[b.start]; + int visualTop = lineTop[b.start]; - int topY; - if (isHeader[b.start]) { - // Header start - constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2 - topY = visualTop - BUBBLE_PAD_TOP_HEADER; - } else { - // Body start - bool thisLineHasEmote = false; - for (int e = 0; e < numEmotes; ++e) { - if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) { - thisLineHasEmote = true; - break; + int topY; + if (isHeader[b.start]) { + // Header start + constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2 + topY = visualTop - BUBBLE_PAD_TOP_HEADER; + } else { + // Body start + bool thisLineHasEmote = false; + for (int e = 0; e < numEmotes; ++e) { + if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) { + thisLineHasEmote = true; + break; + } } + if (thisLineHasEmote) { + constexpr int EMOTE_PADDING_ABOVE = 4; + visualTop -= EMOTE_PADDING_ABOVE; + } + topY = visualTop - BUBBLE_PAD_Y; } - if (thisLineHasEmote) { - constexpr int EMOTE_PADDING_ABOVE = 4; - visualTop -= EMOTE_PADDING_ABOVE; + int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]); + int bottomY = visualBottom + BUBBLE_PAD_Y; + + if (bi + 1 < blocks.size()) { + int nextHeaderIndex = (int)blocks[bi + 1].start; + int nextTop = lineTop[nextHeaderIndex]; + int maxBottom = nextTop - 1 - bubbleGapY; + if (bottomY > maxBottom) + bottomY = maxBottom; } - topY = visualTop - BUBBLE_PAD_Y; - } - int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]); - int bottomY = visualBottom + BUBBLE_PAD_Y; - if (bi + 1 < blocks.size()) { - int nextHeaderIndex = (int)blocks[bi + 1].start; - int nextTop = lineTop[nextHeaderIndex]; - int maxBottom = nextTop - 1 - bubbleGapY; - if (bottomY > maxBottom) - bottomY = maxBottom; - } + if (bottomY <= topY + 2) + continue; - if (bottomY <= topY + 2) - continue; + if (bottomY < contentTop || topY > contentBottom - 1) + continue; - if (bottomY < contentTop || topY > contentBottom - 1) - continue; + int maxLineW = 0; - int maxLineW = 0; - - for (size_t i = b.start; i <= b.end; ++i) { - int w = 0; - if (isHeader[i]) { - w = display->getStringWidth(cachedLines[i].c_str()); - if (b.mine) - w += 12; // room for ACK/NACK/relay mark - } else { - w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); + for (size_t i = b.start; i <= b.end; ++i) { + int w = 0; + if (isHeader[i]) { + w = display->getStringWidth(cachedLines[i].c_str()); + if (b.mine) + w += 12; // room for ACK/NACK/relay mark + } else { + w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); + } + if (w > maxLineW) + maxLineW = w; } - if (w > maxLineW) - maxLineW = w; - } - - int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (BUBBLE_PAD_X * 2)); - int bubbleH = (bottomY - topY) + 1; - int bubbleX = 0; - if (b.mine) { - bubbleX = rightEdge - bubbleW; - } else { - bubbleX = x; - } - if (bubbleX < x) - bubbleX = x; - if (bubbleX + bubbleW > rightEdge) - bubbleW = std::max(1, rightEdge - bubbleX); - - if (bubbleW > 1 && bubbleH > 1) { - int x1 = bubbleX + bubbleW - 1; - int y1 = topY + bubbleH - 1; + int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (textIndent * 2)); + int bubbleH = (bottomY - topY) + 1; + int bubbleX = 0; if (b.mine) { - // Send Message (Right side) - display->drawRect(x1 + 2 - bubbleW, y1 - bubbleH, bubbleW, bubbleH); - // Top Right Corner - display->drawRect(x1, topY, 2, 1); - display->drawRect(x1, topY, 1, 2); - // Bottom Right Corner - display->drawRect(x1 - 1, bottomY - 2, 2, 1); - display->drawRect(x1, bottomY - 3, 1, 2); - // Knock the corners off to make a bubble - display->setColor(BLACK); - display->drawRect(x1 - bubbleW, topY - 1, 1, 1); - display->drawRect(x1 - bubbleW, bottomY - 1, 1, 1); - display->setColor(WHITE); + bubbleX = rightEdge - bubbleW; } else { - // Received Message (Left Side) - display->drawRect(bubbleX, topY, bubbleW + 1, bubbleH); - // Top Left Corner - display->drawRect(bubbleX + 1, topY + 1, 2, 1); - display->drawRect(bubbleX + 1, topY + 1, 1, 2); - // Bottom Left Corner - display->drawRect(bubbleX + 1, bottomY - 1, 2, 1); - display->drawRect(bubbleX + 1, bottomY - 2, 1, 2); - // Knock the corners off to make a bubble - display->setColor(BLACK); - display->drawRect(bubbleX + bubbleW, topY, 1, 1); - display->drawRect(bubbleX + bubbleW, bottomY, 1, 1); - display->setColor(WHITE); + bubbleX = x; + } + if (bubbleX < x) + bubbleX = x; + if (bubbleX + bubbleW > rightEdge) + bubbleW = std::max(1, rightEdge - bubbleX); + + // Draw rounded rectangle bubble + if (bubbleW > BUBBLE_RADIUS * 2 && bubbleH > BUBBLE_RADIUS * 2) { + const int r = BUBBLE_RADIUS; + const int bx = bubbleX; + const int by = topY; + const int bw = bubbleW; + const int bh = bubbleH; + + // Draw the 4 corner arcs using drawCircleQuads + display->drawCircleQuads(bx + r, by + r, r, 0x2); // Top-left + display->drawCircleQuads(bx + bw - r - 1, by + r, r, 0x1); // Top-right + display->drawCircleQuads(bx + r, by + bh - r - 1, r, 0x4); // Bottom-left + display->drawCircleQuads(bx + bw - r - 1, by + bh - r - 1, r, 0x8); // Bottom-right + + // Draw the 4 edges between corners + display->drawHorizontalLine(bx + r, by, bw - 2 * r); // Top edge + display->drawHorizontalLine(bx + r, by + bh - 1, bw - 2 * r); // Bottom edge + display->drawVerticalLine(bx, by + r, bh - 2 * r); // Left edge + display->drawVerticalLine(bx + bw - 1, by + r, bh - 2 * r); // Right edge + } else if (bubbleW > 1 && bubbleH > 1) { + // Fallback to simple rectangle for very small bubbles + display->drawRect(bubbleX, topY, bubbleW, bubbleH); } } - } + } // end if (showBubbles) // Render visible lines int lineY = yOffset; @@ -916,11 +911,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int headerX; if (isMine[i]) { // push header left to avoid overlap with scrollbar - headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT; + headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - (showBubbles ? textIndent : 0); if (headerX < LEFT_MARGIN) headerX = LEFT_MARGIN; } else { - headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT; + headerX = x + textIndent; } display->drawString(headerX, lineY, cachedLines[i].c_str()); @@ -960,14 +955,13 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 if (isMine[i]) { // Calculate actual rendered width including emotes int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); - int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT; + int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - (showBubbles ? textIndent : 0); if (rightX < LEFT_MARGIN) rightX = LEFT_MARGIN; drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes); } else { - drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes, - numEmotes); + drawStringWithEmotes(display, x + textIndent, lineY, cachedLines[i], emotes, numEmotes); } } } diff --git a/src/main.cpp b/src/main.cpp index d9773dfb9..b9ef2f9fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -929,6 +929,13 @@ void setup() service = new MeshService(); service->init(); + // Set osk_found for trackball/encoder devices BEFORE setupModules so CannedMessageModule can detect it +#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2) +#ifndef HAS_PHYSICAL_KEYBOARD + osk_found = true; +#endif +#endif + // Now that the mesh service is created, create any modules setupModules(); @@ -1019,12 +1026,6 @@ void setup() #endif #endif -#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2) -#ifndef HAS_PHYSICAL_KEYBOARD - osk_found = true; -#endif -#endif - #if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER // Start web server thread. webServerThread = new WebServerThread(); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 1d2fbf8fd..f76877e65 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -574,6 +574,10 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR; #endif +#if defined(TFT_WIDTH) && defined(TFT_HEIGHT) && (TFT_WIDTH >= 200 || TFT_HEIGHT >= 200) + config.display.enable_message_bubbles = true; +#endif + #ifdef USERPREFS_CONFIG_DEVICE_ROLE // Restrict ROUTER*, LOST AND FOUND roles for security reasons if (IS_ONE_OF(USERPREFS_CONFIG_DEVICE_ROLE, meshtastic_Config_DeviceConfig_Role_ROUTER, diff --git a/src/modules/KeyVerificationModule.cpp b/src/modules/KeyVerificationModule.cpp index 3b8225763..6d0255d53 100644 --- a/src/modules/KeyVerificationModule.cpp +++ b/src/modules/KeyVerificationModule.cpp @@ -123,7 +123,7 @@ bool KeyVerificationModule::sendInitialRequest(NodeNum remoteNode) // generate nonce updateState(); if (currentState != KEY_VERIFICATION_IDLE) { - IF_SCREEN(graphics::menuHandler::menuQueue = graphics::menuHandler::throttle_message;) + IF_SCREEN(graphics::menuHandler::menuQueue = graphics::menuHandler::ThrottleMessage;) return false; } currentNonce = random(); @@ -259,7 +259,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber) p->priority = meshtastic_MeshPacket_Priority_HIGH; service->sendToMesh(p, RX_SRC_LOCAL, true); currentState = KEY_VERIFICATION_SENDER_AWAITING_USER; - IF_SCREEN(screen->requestMenu(graphics::menuHandler::key_verification_final_prompt);) + IF_SCREEN(screen->requestMenu(graphics::menuHandler::KeyVerificationFinalPrompt);) meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); cn->level = meshtastic_LogRecord_Level_WARNING; sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message); From a60e7cfe626fa82e43b2838f9eb94b64b461ef3b Mon Sep 17 00:00:00 2001 From: Jason P Date: Sat, 7 Feb 2026 19:43:26 -0600 Subject: [PATCH 102/223] Add Slash Key to VirtualKeyboard (#9563) Addition of ? and / to the virtual Keyboard via short and long press --- src/graphics/VirtualKeyboard.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/graphics/VirtualKeyboard.cpp b/src/graphics/VirtualKeyboard.cpp index a24f5b15c..52f0195b3 100644 --- a/src/graphics/VirtualKeyboard.cpp +++ b/src/graphics/VirtualKeyboard.cpp @@ -429,6 +429,10 @@ void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool c = c - 'a' + 'A'; } keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c); + // Show the common "/" pairing next to "?" like on a real keyboard + if (key.type == VK_CHAR && key.character == '?') { + keyText = "?/"; + } } int textWidth = display->getStringWidth(keyText.c_str()); @@ -518,9 +522,13 @@ char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress) char c = key.character; - // Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings - if (isLongPress && c >= 'a' && c <= 'z') { - c = (char)(c - 'a' + 'A'); + // Long-press: letters become uppercase; for "?" provide "/" like a typical keyboard + if (isLongPress) { + if (c >= 'a' && c <= 'z') { + c = (char)(c - 'a' + 'A'); + } else if (c == '?') { + c = '/'; + } } return c; From eb145f8adc70cae3fd46b4603c0467e2e4a268b2 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sat, 7 Feb 2026 22:30:14 -0600 Subject: [PATCH 103/223] Add support for CW2015 LiPo battery fuel gauge (#9564) * Add support for CW2015 LiPo battery fuel gauge * Address Copilot's concerns, minor fixups --- src/Power.cpp | 99 ++++++++++++++++++++++++++++++++--- src/configuration.h | 1 + src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 12 ++++- src/power.h | 6 ++- 5 files changed, 109 insertions(+), 12 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index a7da6f7a9..6ee658f2b 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -692,7 +692,9 @@ bool Power::setup() bool found = false; if (axpChipInit()) { found = true; - } else if (lipoInit()) { + } else if (cw2015Init()) { + found = true; + } else if (max17048Init()) { found = true; } else if (lipoChargerInit()) { found = true; @@ -1321,7 +1323,7 @@ bool Power::axpChipInit() /** * Wrapper class for an I2C MAX17048 Lipo battery sensor. */ -class LipoBatteryLevel : public HasBatteryLevel +class MAX17048BatteryLevel : public HasBatteryLevel { private: MAX17048Singleton *max17048 = nullptr; @@ -1369,18 +1371,18 @@ class LipoBatteryLevel : public HasBatteryLevel virtual bool isCharging() override { return max17048->isBatteryCharging(); } }; -LipoBatteryLevel lipoLevel; +MAX17048BatteryLevel max17048Level; /** * Init the Lipo battery level sensor */ -bool Power::lipoInit() +bool Power::max17048Init() { - bool result = lipoLevel.runOnce(); - LOG_DEBUG("Power::lipoInit lipo sensor is %s", result ? "ready" : "not ready yet"); + bool result = max17048Level.runOnce(); + LOG_DEBUG("Power::max17048Init lipo sensor is %s", result ? "ready" : "not ready yet"); if (!result) return false; - batteryLevel = &lipoLevel; + batteryLevel = &max17048Level; return true; } @@ -1388,7 +1390,88 @@ bool Power::lipoInit() /** * The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel */ -bool Power::lipoInit() +bool Power::max17048Init() +{ + return false; +} +#endif + +#if !MESHTASTIC_EXCLUDE_I2C && HAS_CW2015 + +class CW2015BatteryLevel : public AnalogBatteryLevel +{ + public: + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override + { + int data = -1; + Wire.beginTransmission(CW2015_ADDR); + Wire.write(0x04); + if (Wire.endTransmission() == 0) { + if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) { + data = Wire.read(); + } + } + return data; + } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override + { + uint16_t mv = 0; + Wire.beginTransmission(CW2015_ADDR); + Wire.write(0x02); + if (Wire.endTransmission() == 0) { + if (Wire.requestFrom(CW2015_ADDR, (uint8_t)2)) { + mv = Wire.read(); + mv <<= 8; + mv |= Wire.read(); + // Voltage is read in 305uV units, convert to mV + mv = mv * 305 / 1000; + } + } + return mv; + } +}; + +CW2015BatteryLevel cw2015Level; + +/** + * Init the CW2015 battery level sensor + */ +bool Power::cw2015Init() +{ + + Wire.beginTransmission(CW2015_ADDR); + uint8_t getInfo[] = {0x0a, 0x00}; + Wire.write(getInfo, 2); + Wire.endTransmission(); + delay(10); + Wire.beginTransmission(CW2015_ADDR); + Wire.write(0x00); + bool result = false; + if (Wire.endTransmission() == 0) { + if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) { + uint8_t data = Wire.read(); + LOG_DEBUG("CW2015 init read data: 0x%x", data); + if (data == 0x73) { + result = true; + batteryLevel = &cw2015Level; + } + } + } + return result; +} + +#else +/** + * The CW2015 battery level sensor is unavailable - default to AnalogBatteryLevel + */ +bool Power::cw2015Init() { return false; } diff --git a/src/configuration.h b/src/configuration.h index 66fa4492d..7b7c603ba 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -233,6 +233,7 @@ along with this program. If not, see . #define NAU7802_ADDR 0x2A #define MAX30102_ADDR 0x57 #define SCD4X_ADDR 0x62 +#define CW2015_ADDR 0x62 #define MLX90614_ADDR_DEF 0x5A #define CGRADSENS_ADDR 0x66 #define LTR390UV_ADDR 0x53 diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index dffcd8fb6..0edd5e7a7 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -88,7 +88,8 @@ class ScanI2C BH1750, DA217, CHSC6X, - CST226SE + CST226SE, + CW2015 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index c6ef34846..445e9342c 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -541,7 +541,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address); - SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address); + case SCD4X_ADDR: { + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x8), 1); + if (registerValue == 0x18) { + logFoundDevice("CW2015", (uint8_t)addr.address); + type = CW2015; + } else { + logFoundDevice("SCD4X", (uint8_t)addr.address); + type = SCD4X; + } + break; + } SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address); #ifdef HAS_TPS65233 SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address); diff --git a/src/power.h b/src/power.h index e4b456d3b..b129e2b74 100644 --- a/src/power.h +++ b/src/power.h @@ -103,8 +103,10 @@ class Power : private concurrency::OSThread bool axpChipInit(); /// Setup a simple ADC input based battery sensor bool analogInit(); - /// Setup a Lipo battery level sensor - bool lipoInit(); + /// Setup cw2015 battery level sensor + bool cw2015Init(); + /// Setup a 17048 battery level sensor + bool max17048Init(); /// Setup a Lipo charger bool lipoChargerInit(); /// Setup a meshSolar battery sensor From 4ce554e09d8b8758edc5829955f6658c2d26455b Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 8 Feb 2026 06:49:30 -0600 Subject: [PATCH 104/223] Make LED_POWER blip even in critical battery (#9545) --- src/modules/StatusLEDModule.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 644799c9b..7acf750c2 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -93,19 +93,17 @@ int32_t StatusLEDModule::runOnce() my_interval = 250; if (POWER_LED_starttime + 2000 < millis()) { doing_fast_blink = false; + CHARGE_LED_state = LED_STATE_OFF; } - } else { - CHARGE_LED_state = LED_STATE_OFF; } + } - } else { - if (doing_fast_blink) { + if (power_state != charging && power_state != charged && !doing_fast_blink) { + if (CHARGE_LED_state == LED_STATE_ON) { CHARGE_LED_state = LED_STATE_OFF; - doing_fast_blink = false; my_interval = 999; } else { CHARGE_LED_state = LED_STATE_ON; - doing_fast_blink = true; my_interval = 1; } } From 7cbab4838c6518d485324c6dec0c143f46eeffed Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Sun, 8 Feb 2026 14:08:09 +0100 Subject: [PATCH 105/223] Feat/add sen5x (#7245) * Move PMSA003I to separate class and update AQ telemetry * AirQualityTelemetry module not depend on PM sensor presence * Remove commented line * Fixes on PMS class * Add missing warmup period to wakeUp function * Fixes on compilation for different variants * Add functions to check for I2C bus speed and set it * Add ScreenFonts.h Co-authored-by: Hannes Fuchs * PMSA003I 1st round test * Fix I2C scan speed * Fix minor issues and bring back I2C SPEED def * Remove PMSA003I library as its no longer needed * Remove unused I2C speed functions and cleanup * Cleanup of SEN5X specific code added from switching branches * Remove SCAN_I2C_CLOCK_SPEED block as its not needed * Remove associated functions for setting I2C speed * Unify build epoch to add flag in platformio-custom.py (#7917) * Unify build_epoch replacement logic in platformio-custom * Missed one * Fix build error in rak_wismesh_tap_v2 (#7905) In the logs was: "No screen resolution defined in build_flags. Please define DISPLAY_SIZE." set according to similar devices. * Put guards in place around debug heap operations (#7955) * Put guards in place around debug heap operations * Add macros to clean up code * Add pointer as well * Cleanup * Fix memory leak in NextHopRouter: always free packet copy when removing from pending * Formatting * Only queue 2 client notification * Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap * Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap * Update protobufs (#7973) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs * Trunk * Trunk * Static memory pool allocation (#7966) * Static memory pool * Initializer * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs --------- Co-authored-by: WillyJL * Portduino dynamic alloc * Missed * Drop the limit * Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix json report crashes on esp32 (#7978) * Tweak maximums * Fix DRAM overflow on old esp32 targets * Guard bad time warning logs using GPS_DEBUG (#7897) In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy. In combination, these result in a spamming of the logs when a bad time is found When the GPS is active, we're calling the GPS thread every 0.2secs. So this log could be printed 4,500 times in a no-lock scenario :) Reserve this experience for developers using GPS_DEBUG. Fixes https://github.com/meshtastic/firmware/issues/7896 * Scale probe buffer size based on current baud rate (#7975) * Scale probe buffer size based on current baud rate * Throttle bad time validation logging and fix time comparison logic * Remove comment * Missed the other instances * Copy pasta * Fix GPS gm_mktime memory leak (#7981) * Fix overflow of time value (#7984) * Fix overflow of time value * Revert "Fix overflow of time value" This reverts commit 084796920179e80a7500d36c25fd4d82b3ef4214. * That got boogered up * Remove PMSA003 include from modules * Add flag to exclude air quality module * Rework PMSA003I to align with new I2C scanner * Reworks AQ telemetry to match new dynamic allocation method * Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements * Reworks PMSA003I * Move add sensor template to separate file * Split telemetry on screen options * Add variable I2C clock compile flag * Added to Seeed Xiao S3 as demo * Fix drawFrame in AQ module * Module settings override to i2cScan module function * Move to CAN_RECLOCK_I2C per architecture * Add reclock function in TelemetrySensor.cpp * Add flag in ESP32 common * Minor fix * Move I2C reclock function to src/detect * Fix uninitMemberVar errors and compile issue * Make sleep, wakeUp functions generic * Fix STM32 builds * Add exclude AQ sensor to builds that have environmental sensor excludes * Add includes to AddI2CSensorTemplate.h * SEN5X first pass * WIP Sen5X functions * Further (non-working) progress in SEN5X * WIP Sen5X functions * Changes on SEN5X library - removing pm_env as well * Small cleanup of SEN5X sensors * Minor change for SEN5X detection * Remove dup code * Enable PM sensor before sending telemetry. This enables the PM sensor for a predefined period to allow for warmup. Once telemetry is sent, the sensor shuts down again. * Small cleanups in SEN5X sensor * Add dynamic measurement interval for SEN5X * Only disable SEN5X if enough time after reading. * Idle for SEN5X on communication error * Cleanup of logs and remove unnecessary delays * Small TODO * Settle on uint16_t for SEN5X PM data * Make AQTelemetry sensors non-exclusive * Implementation of cleaning in FS prefs and cleanup * Remove unnecessary LOGS * Add cleaning date storage in FS * Report non-cumulative PN * Bring back detection code for SEN5X after branch rebase * Add placeholder for admin message * Add VOC measurements and persistence (WIP) * Adds VOC measurements and state * Still not working on VOC Index persistence * Should it stay in continuous mode? * Add one-shot mode config flag to SEN5X * Add nan checks on sensor data from SEN5X * Working implementation on VOCState * Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h) * Adds conditions for stability and sensor state * Fixes on VOC state and mode swtiching * Adds a new RHT/Gas only mode, with 3600s stabilization time * Fixes the VOCState buffer mismatch * Fixes SEN50/54/55 model mistake * Adapt SEN5X to new sensor list structure. Improve reclock. * Improve reClockI2C conditions for different variants * Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery * Add SEN5X * Fix merge errors * Update library dependencies in platformio.ini * Fix unitialized variables in SEN5X constructor * Fix missing import * Cleanup of SEN5X class * Exclude AQ sensor from wio-e5 due to flash limitations * Fix I2C clock change logic * Fix trunk * Fix on condition in reclock * Add check on polling interval of sen5x --------- Co-authored-by: Hannes Fuchs Co-authored-by: Nashui-Yan Co-authored-by: Ben Meadors Co-authored-by: Tom Fifield Co-authored-by: Mike Robbins Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: WillyJL Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 3 +- src/configuration.h | 1 + src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 74 +- src/detect/reClockI2C.cpp | 31 + src/detect/reClockI2C.h | 44 +- src/main.cpp | 1 - src/modules/Telemetry/AirQualityTelemetry.cpp | 80 +- .../Telemetry/Sensor/PMSA003ISensor.cpp | 92 +- src/modules/Telemetry/Sensor/PMSA003ISensor.h | 5 + src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 957 ++++++++++++++++++ src/modules/Telemetry/Sensor/SEN5XSensor.h | 170 ++++ .../Telemetry/Sensor/TelemetrySensor.h | 10 +- src/serialization/MeshPacketSerializer.cpp | 24 +- .../MeshPacketSerializer_nRF52.cpp | 18 +- variants/stm32/wio-e5/platformio.ini | 1 + 16 files changed, 1401 insertions(+), 113 deletions(-) create mode 100644 src/detect/reClockI2C.cpp create mode 100644 src/modules/Telemetry/Sensor/SEN5XSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SEN5XSensor.h diff --git a/platformio.ini b/platformio.ini index 1bdaf0a2d..bc812dc6f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -213,6 +213,7 @@ lib_deps = sensirion/Sensirion Core@0.7.2 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 + ; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) [environmental_extra_no_bsec] lib_deps = @@ -239,4 +240,4 @@ lib_deps = # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core sensirion/Sensirion Core@0.7.2 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x - sensirion/Sensirion I2C SCD4x@1.1.0 \ No newline at end of file + sensirion/Sensirion I2C SCD4x@1.1.0 diff --git a/src/configuration.h b/src/configuration.h index 66fa4492d..744597b3c 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -241,6 +241,7 @@ along with this program. If not, see . #define BQ27220_ADDR 0x55 // same address as TDECK_KB #define BQ25896_ADDR 0x6B #define LTR553ALS_ADDR 0x23 +#define SEN5X_ADDR 0x69 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index dffcd8fb6..3910ddf64 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -88,7 +88,8 @@ class ScanI2C BH1750, DA217, CHSC6X, - CST226SE + CST226SE, + SEN5X } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index c6ef34846..6655addf1 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -8,6 +8,7 @@ #endif #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) #include "meshUtils.h" // vformat + #endif bool in_array(uint8_t *array, int size, uint8_t lookfor) @@ -114,6 +115,45 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation return value; } +/// for SEN5X detection +// Note, this code needs to be called before setting the I2C bus speed +// for the screen at high speed. The speed needs to be at 100kHz, otherwise +// detection will not work +String readSEN5xProductName(TwoWire *i2cBus, uint8_t address) +{ + uint8_t cmd[] = {0xD0, 0x14}; + uint8_t response[48] = {0}; + + i2cBus->beginTransmission(address); + i2cBus->write(cmd, 2); + if (i2cBus->endTransmission() != 0) + return ""; + + delay(20); + if (i2cBus->requestFrom(address, (uint8_t)48) != 48) + return ""; + + for (int i = 0; i < 48 && i2cBus->available(); ++i) { + response[i] = i2cBus->read(); + } + + char productName[33] = {0}; + int j = 0; + for (int i = 0; i < 48 && j < 32; i += 3) { + if (response[i] >= 32 && response[i] <= 126) + productName[j++] = response[i]; + else + break; + + if (response[i + 1] >= 32 && response[i + 1] <= 126) + productName[j++] = response[i + 1]; + else + break; + } + + return String(productName); +} + #define SCAN_SIMPLE_CASE(ADDR, T, ...) \ case ADDR: \ logFoundDevice(__VA_ARGS__); \ @@ -568,8 +608,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; - case ICM20948_ADDR: // same as BMX160_ADDR + case ICM20948_ADDR: // same as BMX160_ADDR and SEN5X_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR + // ICM20948 Register check registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); #ifdef HAS_ICM20948 type = ICM20948; @@ -580,14 +621,31 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = ICM20948; logFoundDevice("ICM20948", (uint8_t)addr.address); break; - } else if (addr.address == BMX160_ADDR) { - type = BMX160; - logFoundDevice("BMX160", (uint8_t)addr.address); - break; } else { - type = MPU6050; - logFoundDevice("MPU6050", (uint8_t)addr.address); - break; + String prod = ""; + prod = readSEN5xProductName(i2cBus, addr.address); + if (prod.startsWith("SEN55")) { + type = SEN5X; + logFoundDevice("Sensirion SEN55", addr.address); + break; + } else if (prod.startsWith("SEN54")) { + type = SEN5X; + logFoundDevice("Sensirion SEN54", addr.address); + break; + } else if (prod.startsWith("SEN50")) { + type = SEN5X; + logFoundDevice("Sensirion SEN50", addr.address); + break; + } + if (addr.address == BMX160_ADDR) { + type = BMX160; + logFoundDevice("BMX160", (uint8_t)addr.address); + break; + } else { + type = MPU6050; + logFoundDevice("MPU6050", (uint8_t)addr.address); + break; + } } break; diff --git a/src/detect/reClockI2C.cpp b/src/detect/reClockI2C.cpp new file mode 100644 index 000000000..60cd3c808 --- /dev/null +++ b/src/detect/reClockI2C.cpp @@ -0,0 +1,31 @@ +#include "reClockI2C.h" +#include "ScanI2CTwoWire.h" + +uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus, bool force) +{ + + uint32_t currentClock = 0; + + /* See https://github.com/arduino/Arduino/issues/11457 + Currently, only ESP32 can getClock() + While all cores can setClock() + https://github.com/sandeepmistry/arduino-nRF5/blob/master/libraries/Wire/Wire.h#L50 + https://github.com/earlephilhower/arduino-pico/blob/master/libraries/Wire/src/Wire.h#L60 + https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/Wire/src/Wire.h#L103 + For cases when I2C speed is different to the ones defined by sensors (see defines in sensor classes) + we need to reclock I2C and set it back to the previous desired speed. + Only for cases where we can know OR predefine the speed, we can do this. + */ + +// TODO add getClock function or return a predefined clock speed per variant? +#ifdef CAN_RECLOCK_I2C + currentClock = i2cBus->getClock(); +#endif + + if ((currentClock != desiredClock) || force) { + LOG_DEBUG("Changing I2C clock to %u", desiredClock); + i2cBus->setClock(desiredClock); + } + + return currentClock; +} diff --git a/src/detect/reClockI2C.h b/src/detect/reClockI2C.h index 689e88d6f..9c53efc4f 100644 --- a/src/detect/reClockI2C.h +++ b/src/detect/reClockI2C.h @@ -1,41 +1,11 @@ -#ifdef CAN_RECLOCK_I2C + +#ifndef RECLOCK_I2C_ +#define RECLOCK_I2C_ + #include "ScanI2CTwoWire.h" +#include +#include -uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus) -{ +uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus, bool force); - uint32_t currentClock; - - /* See https://github.com/arduino/Arduino/issues/11457 - Currently, only ESP32 can getClock() - While all cores can setClock() - https://github.com/sandeepmistry/arduino-nRF5/blob/master/libraries/Wire/Wire.h#L50 - https://github.com/earlephilhower/arduino-pico/blob/master/libraries/Wire/src/Wire.h#L60 - https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/Wire/src/Wire.h#L103 - For cases when I2C speed is different to the ones defined by sensors (see defines in sensor classes) - we need to reclock I2C and set it back to the previous desired speed. - Only for cases where we can know OR predefine the speed, we can do this. - */ - -#ifdef ARCH_ESP32 - currentClock = i2cBus->getClock(); -#elif defined(ARCH_NRF52) - // TODO add getClock function or return a predefined clock speed per variant? - return 0; -#elif defined(ARCH_RP2040) - // TODO add getClock function or return a predefined clock speed per variant - return 0; -#elif defined(ARCH_STM32WL) - // TODO add getClock function or return a predefined clock speed per variant - return 0; -#else - return 0; -#endif - - if (currentClock != desiredClock) { - LOG_DEBUG("Changing I2C clock to %u", desiredClock); - i2cBus->setClock(desiredClock); - } - return currentClock; -} #endif diff --git a/src/main.cpp b/src/main.cpp index b9ef2f9fe..415d23f44 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -713,7 +713,6 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); - #endif #ifdef HAS_SDCARD diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 01f5da2c6..2ccb147c3 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -10,7 +10,6 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" -#include "Sensor/AddI2CSensorTemplate.h" #include "UnitConversions.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" @@ -20,7 +19,9 @@ #include // Sensors +#include "Sensor/AddI2CSensorTemplate.h" #include "Sensor/PMSA003ISensor.h" +#include "Sensor/SEN5XSensor.h" void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { @@ -42,6 +43,7 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) // order by priority of metrics/values (low top, high bottom) addSensor(i2cScanner, ScanI2C::DeviceType::PMSA003I); + addSensor(i2cScanner, ScanI2C::DeviceType::SEN5X); } int32_t AirQualityTelemetryModule::runOnce() @@ -85,10 +87,27 @@ int32_t AirQualityTelemetryModule::runOnce() } // Wake up the sensors that need it - LOG_INFO("Waking up sensors"); + LOG_INFO("Waking up sensors..."); for (TelemetrySensor *sensor : sensors) { - if (!sensor->isActive()) { - return sensor->wakeUp(); + if (!sensor->canSleep()) { + LOG_DEBUG("%s sensor doesn't have sleep feature. Skipping", sensor->sensorName); + } else if (((lastSentToMesh == 0) || + !Throttle::isWithinTimespanMs(lastSentToMesh - sensor->wakeUpTimeMs(), + Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && + airTime->isTxAllowedAirUtil()) { + if (!sensor->isActive()) { + LOG_DEBUG("Waking up: %s", sensor->sensorName); + return sensor->wakeUp(); + } else { + int32_t pendingForReadyMs = sensor->pendingForReadyMs(); + LOG_DEBUG("%s. Pending for ready %ums", sensor->sensorName, pendingForReadyMs); + if (pendingForReadyMs) { + return pendingForReadyMs; + } + } } } @@ -109,9 +128,18 @@ int32_t AirQualityTelemetryModule::runOnce() } // Send to sleep sensors that consume power - LOG_INFO("Sending sensors to sleep"); + LOG_DEBUG("Sending sensors to sleep"); for (TelemetrySensor *sensor : sensors) { - sensor->sleep(); + if (sensor->isActive() && sensor->canSleep()) { + if (sensor->wakeUpTimeMs() < Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, + numOnlineNodes)) { + LOG_DEBUG("Disabling %s until next period", sensor->sensorName); + sensor->sleep(); + } else { + LOG_DEBUG("Sensor stays enabled due to warm up period"); + } + } } } return min(sendToPhoneIntervalMs, result); @@ -158,8 +186,7 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta const auto &m = telemetry.variant.air_quality_metrics; // Check if any telemetry field has valid data - bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_pm10_environmental || - m.has_pm25_environmental || m.has_pm100_environmental; + bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard; if (!hasAny) { display->drawString(x, currentY, "No Telemetry"); @@ -225,9 +252,10 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard, t->variant.air_quality_metrics.pm100_standard); - LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i", - t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental, - t->variant.air_quality_metrics.pm100_environmental); + // TODO - Decide what to do with these + // LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i", + // t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental, + // t->variant.air_quality_metrics.pm100_environmental); #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -247,10 +275,8 @@ bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag; m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero; - // TODO - Should we check for sensor state here? - // If a sensor is sleeping, we should know and check to wake it up for (TelemetrySensor *sensor : sensors) { - LOG_INFO("Reading AQ sensors"); + LOG_DEBUG("Reading %s", sensor->sensorName); valid = valid && sensor->getMetrics(m); hasSensor = true; } @@ -291,12 +317,14 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag; m.time = getTime(); + if (getAirQualityTelemetry(&m)) { - LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u, \ - pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u", - m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard, - m.variant.air_quality_metrics.pm100_standard, m.variant.air_quality_metrics.pm10_environmental, - m.variant.air_quality_metrics.pm25_environmental, m.variant.air_quality_metrics.pm100_environmental); + LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u", m.variant.air_quality_metrics.pm10_standard, + m.variant.air_quality_metrics.pm25_standard, m.variant.air_quality_metrics.pm100_standard); + if (m.variant.air_quality_metrics.has_pm10_environmental) + LOG_INFO("pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u", + m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental, + m.variant.air_quality_metrics.pm100_environmental); meshtastic_MeshPacket *p = allocDataProtobuf(m); p->to = dest; @@ -331,6 +359,20 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) LOG_DEBUG("Start next execution in 5s, then sleep"); setIntervalFromNow(FIVE_SECONDS_MS); } + + if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) { + meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed(); + notification->level = meshtastic_LogRecord_Level_INFO; + notification->time = getValidTime(RTCQualityFromNet); + sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment", + Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs) / + 1000U); + service->sendClientNotification(notification); + sleepOnNextExecution = true; + LOG_DEBUG("Start next execution in 5s, then sleep"); + setIntervalFromNow(FIVE_SECONDS_MS); + } } return true; } diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index 2225a4d87..bc067c04c 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -21,26 +21,29 @@ bool PMSA003ISensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) _bus = bus; _address = dev->address.address; -#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) - uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus); - if (!currentClock) { - LOG_WARN("PMSA003I can't be used at this clock speed"); - return false; - } -#endif +#ifdef PMSA003I_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* PMSA003I_I2C_CLOCK_SPEED */ _bus->beginTransmission(_address); if (_bus->endTransmission() != 0) { - LOG_WARN("PMSA003I not found on I2C at 0x12"); + LOG_WARN("%s not found on I2C at 0x12", sensorName); return false; } #if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) - reClockI2C(currentClock, _bus); + reClockI2C(currentClock, _bus, false); #endif status = 1; - LOG_INFO("PMSA003I Enabled"); + LOG_INFO("%s Enabled", sensorName); initI2CSensor(); return true; @@ -49,30 +52,37 @@ bool PMSA003ISensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) { if (!isActive()) { - LOG_WARN("PMSA003I is not active"); + LOG_WARN("Can't get metrics. %s is not active", sensorName); return false; } -#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) - uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus); -#endif +#ifdef PMSA003I_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* PMSA003I_I2C_CLOCK_SPEED */ _bus->requestFrom(_address, PMSA003I_FRAME_LENGTH); if (_bus->available() < PMSA003I_FRAME_LENGTH) { - LOG_WARN("PMSA003I read failed: incomplete data (%d bytes)", _bus->available()); + LOG_WARN("%s read failed: incomplete data (%d bytes)", sensorName, _bus->available()); return false; } -#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) - reClockI2C(currentClock, _bus); -#endif - for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH; i++) { buffer[i] = _bus->read(); } +#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + if (buffer[0] != 0x42 || buffer[1] != 0x4D) { - LOG_WARN("PMSA003I frame header invalid: 0x%02X 0x%02X", buffer[0], buffer[1]); + LOG_WARN("%s frame header invalid: 0x%02X 0x%02X", sensorName, buffer[0], buffer[1]); return false; } @@ -86,7 +96,7 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) receivedChecksum = read16(buffer, PMSA003I_FRAME_LENGTH - 2); if (computedChecksum != receivedChecksum) { - LOG_WARN("PMSA003I checksum failed: computed 0x%04X, received 0x%04X", computedChecksum, receivedChecksum); + LOG_WARN("%s checksum failed: computed 0x%04X, received 0x%04X", sensorName, computedChecksum, receivedChecksum); return false; } @@ -136,20 +146,58 @@ bool PMSA003ISensor::isActive() return state == State::ACTIVE; } +int32_t PMSA003ISensor::wakeUpTimeMs() +{ +#ifdef PMSA003I_ENABLE_PIN + return PMSA003I_WARMUP_MS; +#endif + return 0; +} + +int32_t PMSA003ISensor::pendingForReadyMs() +{ +#ifdef PMSA003I_ENABLE_PIN + + uint32_t now; + now = getTime(); + uint32_t sincePmMeasureStarted = (now - pmMeasureStarted) * 1000; + LOG_DEBUG("%s: Since measure started: %ums", sensorName, sincePmMeasureStarted); + + if (sincePmMeasureStarted < PMSA003I_WARMUP_MS) { + LOG_INFO("%s: not enough time passed since starting measurement", sensorName); + return PMSA003I_WARMUP_MS - sincePmMeasureStarted; + } + return 0; + +#endif + return 0; +} + +bool PMSA003ISensor::canSleep() +{ +#ifdef PMSA003I_ENABLE_PIN + return true; +#endif + return false; +} + void PMSA003ISensor::sleep() { #ifdef PMSA003I_ENABLE_PIN digitalWrite(PMSA003I_ENABLE_PIN, LOW); state = State::IDLE; + pmMeasureStarted = 0; #endif } uint32_t PMSA003ISensor::wakeUp() { #ifdef PMSA003I_ENABLE_PIN - LOG_INFO("Waking up PMSA003I"); + LOG_INFO("Waking up %s", sensorName); digitalWrite(PMSA003I_ENABLE_PIN, HIGH); state = State::ACTIVE; + pmMeasureStarted = getTime(); + return PMSA003I_WARMUP_MS; #endif // No need to wait for warmup if already active diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.h b/src/modules/Telemetry/Sensor/PMSA003ISensor.h index 09b43d620..3fe96888d 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.h +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.h @@ -3,6 +3,7 @@ #if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "RTC.h" #include "TelemetrySensor.h" #define PMSA003I_I2C_CLOCK_SPEED 100000 @@ -19,6 +20,9 @@ class PMSA003ISensor : public TelemetrySensor virtual bool isActive() override; virtual void sleep() override; virtual uint32_t wakeUp() override; + virtual bool canSleep() override; + virtual int32_t wakeUpTimeMs() override; + virtual int32_t pendingForReadyMs() override; private: enum class State { IDLE, ACTIVE }; @@ -26,6 +30,7 @@ class PMSA003ISensor : public TelemetrySensor uint16_t computedChecksum = 0; uint16_t receivedChecksum = 0; + uint32_t pmMeasureStarted = 0; uint8_t buffer[PMSA003I_FRAME_LENGTH]{}; TwoWire *_bus{}; diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp new file mode 100644 index 000000000..fc20ce1f2 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -0,0 +1,957 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR + +#include "../detect/reClockI2C.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "FSCommon.h" +#include "SEN5XSensor.h" +#include "SPILock.h" +#include "SafeFile.h" +#include "TelemetrySensor.h" +#include // FLT_MAX +#include +#include + +SEN5XSensor::SEN5XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SEN5X, "SEN5X") {} + +bool SEN5XSensor::getVersion() +{ + if (!sendCommand(SEN5X_GET_FIRMWARE_VERSION)) { + LOG_ERROR("SEN5X: Error sending version command"); + return false; + } + delay(20); // From Sensirion Datasheet + + uint8_t versionBuffer[12]; + size_t charNumber = readBuffer(&versionBuffer[0], 3); + if (charNumber == 0) { + LOG_ERROR("SEN5X: Error getting data ready flag value"); + return false; + } + + firmwareVer = versionBuffer[0] + (versionBuffer[1] / 10); + hardwareVer = versionBuffer[3] + (versionBuffer[4] / 10); + protocolVer = versionBuffer[5] + (versionBuffer[6] / 10); + + LOG_INFO("SEN5X Firmware Version: %0.2f", firmwareVer); + LOG_INFO("SEN5X Hardware Version: %0.2f", hardwareVer); + LOG_INFO("SEN5X Protocol Version: %0.2f", protocolVer); + + return true; +} + +bool SEN5XSensor::findModel() +{ + if (!sendCommand(SEN5X_GET_PRODUCT_NAME)) { + LOG_ERROR("SEN5X: Error asking for product name"); + return false; + } + delay(50); // From Sensirion Datasheet + + const uint8_t nameSize = 48; + uint8_t name[nameSize]; + size_t charNumber = readBuffer(&name[0], nameSize); + if (charNumber == 0) { + LOG_ERROR("SEN5X: Error getting device name"); + return false; + } + + // We only check the last character that defines the model SEN5X + switch (name[4]) { + case 48: + model = SEN50; + LOG_INFO("SEN5X: found sensor model SEN50"); + break; + case 52: + model = SEN54; + LOG_INFO("SEN5X: found sensor model SEN54"); + break; + case 53: + model = SEN55; + LOG_INFO("SEN5X: found sensor model SEN55"); + break; + } + + return true; +} + +bool SEN5XSensor::sendCommand(uint16_t command) +{ + uint8_t nothing; + return sendCommand(command, ¬hing, 0); +} + +bool SEN5XSensor::sendCommand(uint16_t command, uint8_t *buffer, uint8_t byteNumber) +{ + // At least we need two bytes for the command + uint8_t bufferSize = 2; + + // Add space for CRC bytes (one every two bytes) + if (byteNumber > 0) + bufferSize += byteNumber + (byteNumber / 2); + + uint8_t toSend[bufferSize]; + uint8_t i = 0; + toSend[i++] = static_cast((command & 0xFF00) >> 8); + toSend[i++] = static_cast((command & 0x00FF) >> 0); + + // Prepare buffer with CRC every third byte + uint8_t bi = 0; + if (byteNumber > 0) { + while (bi < byteNumber) { + toSend[i++] = buffer[bi++]; + toSend[i++] = buffer[bi++]; + uint8_t calcCRC = sen5xCRC(&buffer[bi - 2]); + toSend[i++] = calcCRC; + } + } + +#ifdef SEN5X_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SEN5X_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SEN5X_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SEN5X_I2C_CLOCK_SPEED */ + + // Transmit the data + // LOG_DEBUG("Beginning connection to SEN5X: 0x%x. Size: %u", address, bufferSize); + // Note: this delay is necessary to allow for long-buffers + delay(20); + _bus->beginTransmission(_address); + size_t writtenBytes = _bus->write(toSend, bufferSize); + uint8_t i2c_error = _bus->endTransmission(); + +#if defined(SEN5X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + if (writtenBytes != bufferSize) { + LOG_ERROR("SEN5X: Error writting on I2C bus"); + return false; + } + + if (i2c_error != 0) { + LOG_ERROR("SEN5X: Error on I2C communication: %x", i2c_error); + return false; + } + return true; +} + +uint8_t SEN5XSensor::readBuffer(uint8_t *buffer, uint8_t byteNumber) +{ +#ifdef SEN5X_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SEN5X_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SEN5X_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SEN5X_I2C_CLOCK_SPEED */ + + size_t readBytes = _bus->requestFrom(_address, byteNumber); + if (readBytes != byteNumber) { + LOG_ERROR("SEN5X: Error reading I2C bus"); + return 0; + } + + uint8_t i = 0; + uint8_t receivedBytes = 0; + while (readBytes > 0) { + buffer[i++] = _bus->read(); // Just as a reminder: i++ returns i and after that increments. + buffer[i++] = _bus->read(); + uint8_t recvCRC = _bus->read(); + uint8_t calcCRC = sen5xCRC(&buffer[i - 2]); + if (recvCRC != calcCRC) { + LOG_ERROR("SEN5X: Checksum error while receiving msg"); + return 0; + } + readBytes -= 3; + receivedBytes += 2; + } +#if defined(SEN5X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + return receivedBytes; +} + +uint8_t SEN5XSensor::sen5xCRC(uint8_t *buffer) +{ + // This code is based on Sensirion's own implementation + // https://github.com/Sensirion/arduino-core/blob/41fd02cacf307ec4945955c58ae495e56809b96c/src/SensirionCrc.cpp + uint8_t crc = 0xff; + + for (uint8_t i = 0; i < 2; i++) { + + crc ^= buffer[i]; + + for (uint8_t bit = 8; bit > 0; bit--) { + if (crc & 0x80) + crc = (crc << 1) ^ 0x31; + else + crc = (crc << 1); + } + } + + return crc; +} + +void SEN5XSensor::sleep() +{ + // TODO Check this works + idle(true); +} + +bool SEN5XSensor::idle(bool checkState) +{ + // From the datasheet: + // By default, the VOC algorithm resets its state to initial + // values each time a measurement is started, + // even if the measurement was stopped only for a short + // time. So, the VOC index output value needs a long time + // until it is stable again. This can be avoided by + // restoring the previously memorized algorithm state before + // starting the measure mode + + if (checkState) { + // If the stabilisation period is not passed for SEN54 or SEN55, don't go to idle + if (model != SEN50) { + // Get VOC state before going to idle mode + vocValid = false; + if (vocStateFromSensor()) { + vocValid = vocStateValid(); + // Check if we have time, and store it + uint32_t now; // If time is RTCQualityNone, it will return zero + now = getValidTime(RTCQuality::RTCQualityDevice); + if (now) { + // Check if state is valid (non-zero) + vocTime = now; + } + } + + if (vocStateStable() && vocValid) { + saveState(); + } else { + LOG_INFO("SEN5X: Not stopping measurement, vocState is not stable yet!"); + return true; + } + } + } + + if (!oneShotMode) { + LOG_INFO("SEN5X: Not stopping measurement, continuous mode!"); + return true; + } + + // Switch to low-power based on the model + if (model == SEN50) { + if (!sendCommand(SEN5X_STOP_MEASUREMENT)) { + LOG_ERROR("SEN5X: Error stopping measurement"); + return false; + } + state = SEN5X_IDLE; + LOG_INFO("SEN5X: Stop measurement mode"); + } else { + if (!sendCommand(SEN5X_START_MEASUREMENT_RHT_GAS)) { + LOG_ERROR("SEN5X: Error switching to RHT/Gas measurement"); + return false; + } + state = SEN5X_RHTGAS_ONLY; + LOG_INFO("SEN5X: Switch to RHT/Gas only measurement mode"); + } + + delay(200); // From Sensirion Datasheet + pmMeasureStarted = 0; + return true; +} + +bool SEN5XSensor::vocStateRecent(uint32_t now) +{ + if (now) { + uint32_t passed = now - vocTime; // in seconds + + // Check if state is recent, less than 10 minutes (600 seconds) + if (passed < SEN5X_VOC_VALID_TIME && (now > SEN5X_VOC_VALID_DATE)) { + return true; + } + } + return false; +} + +bool SEN5XSensor::vocStateValid() +{ + if (!vocState[0] && !vocState[1] && !vocState[2] && !vocState[3] && !vocState[4] && !vocState[5] && !vocState[6] && + !vocState[7]) { + LOG_DEBUG("SEN5X: VOC state is all 0, invalid"); + return false; + } else { + LOG_DEBUG("SEN5X: VOC state is valid"); + return true; + } +} + +bool SEN5XSensor::vocStateToSensor() +{ + if (model == SEN50) { + return true; + } + + if (!vocStateValid()) { + LOG_INFO("SEN5X: VOC state is invalid, not sending"); + return true; + } + + if (!sendCommand(SEN5X_STOP_MEASUREMENT)) { + LOG_ERROR("SEN5X: Error stoping measurement"); + return false; + } + delay(200); // From Sensirion Datasheet + + LOG_DEBUG("SEN5X: Sending VOC state to sensor"); + LOG_DEBUG("[%u, %u, %u, %u, %u, %u, %u, %u]", vocState[0], vocState[1], vocState[2], vocState[3], vocState[4], vocState[5], + vocState[6], vocState[7]); + + // Note: send command already takes into account the CRC + // buffer size increment needed + if (!sendCommand(SEN5X_RW_VOCS_STATE, vocState, SEN5X_VOC_STATE_BUFFER_SIZE)) { + LOG_ERROR("SEN5X: Error sending VOC's state command'"); + return false; + } + + return true; +} + +bool SEN5XSensor::vocStateFromSensor() +{ + if (model == SEN50) { + return true; + } + + LOG_INFO("SEN5X: Getting VOC state from sensor"); + // Ask VOCs state from the sensor + if (!sendCommand(SEN5X_RW_VOCS_STATE)) { + LOG_ERROR("SEN5X: Error sending VOC's state command'"); + return false; + } + + delay(20); // From Sensirion Datasheet + + // Retrieve the data + // Allocate buffer to account for CRC + size_t receivedNumber = readBuffer(&vocState[0], SEN5X_VOC_STATE_BUFFER_SIZE + (SEN5X_VOC_STATE_BUFFER_SIZE / 2)); + delay(20); // From Sensirion Datasheet + + if (receivedNumber == 0) { + LOG_DEBUG("SEN5X: Error getting VOC's state"); + return false; + } + + // Print the state (if debug is on) + LOG_DEBUG("SEN5X: VOC state retrieved from sensor: [%u, %u, %u, %u, %u, %u, %u, %u]", vocState[0], vocState[1], vocState[2], + vocState[3], vocState[4], vocState[5], vocState[6], vocState[7]); + + return true; +} + +bool SEN5XSensor::loadState() +{ +#ifdef FSCom + spiLock->lock(); + auto file = FSCom.open(sen5XStateFileName, FILE_O_READ); + bool okay = false; + if (file) { + LOG_INFO("%s state read from %s", sensorName, sen5XStateFileName); + pb_istream_t stream = {&readcb, &file, meshtastic_SEN5XState_size}; + + if (!pb_decode(&stream, &meshtastic_SEN5XState_msg, &sen5xstate)) { + LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream)); + } else { + lastCleaning = sen5xstate.last_cleaning_time; + lastCleaningValid = sen5xstate.last_cleaning_valid; + oneShotMode = sen5xstate.one_shot_mode; + + if (model != SEN50) { + vocTime = sen5xstate.voc_state_time; + vocValid = sen5xstate.voc_state_valid; + // Unpack state + vocState[7] = (uint8_t)(sen5xstate.voc_state_array >> 56); + vocState[6] = (uint8_t)(sen5xstate.voc_state_array >> 48); + vocState[5] = (uint8_t)(sen5xstate.voc_state_array >> 40); + vocState[4] = (uint8_t)(sen5xstate.voc_state_array >> 32); + vocState[3] = (uint8_t)(sen5xstate.voc_state_array >> 24); + vocState[2] = (uint8_t)(sen5xstate.voc_state_array >> 16); + vocState[1] = (uint8_t)(sen5xstate.voc_state_array >> 8); + vocState[0] = (uint8_t)sen5xstate.voc_state_array; + } + + // LOG_DEBUG("Loaded lastCleaning %u", lastCleaning); + // LOG_DEBUG("Loaded lastCleaningValid %u", lastCleaningValid); + // LOG_DEBUG("Loaded oneShotMode %s", oneShotMode ? "true" : "false"); + // LOG_DEBUG("Loaded vocTime %u", vocTime); + // LOG_DEBUG("Loaded [%u, %u, %u, %u, %u, %u, %u, %u]", + // vocState[7], vocState[6], vocState[5], vocState[4], vocState[3], vocState[2], vocState[1], vocState[0]); + // LOG_DEBUG("Loaded %svalid VOC state", vocValid ? "" : "in"); + + okay = true; + } + file.close(); + } else { + LOG_INFO("No %s state found (File: %s)", sensorName, sen5XStateFileName); + } + spiLock->unlock(); + return okay; +#else + LOG_ERROR("SEN5X: ERROR - Filesystem not implemented"); +#endif +} + +bool SEN5XSensor::saveState() +{ +#ifdef FSCom + auto file = SafeFile(sen5XStateFileName); + + sen5xstate.last_cleaning_time = lastCleaning; + sen5xstate.last_cleaning_valid = lastCleaningValid; + sen5xstate.one_shot_mode = oneShotMode; + + if (model != SEN50) { + sen5xstate.has_voc_state_time = true; + sen5xstate.has_voc_state_valid = true; + sen5xstate.has_voc_state_array = true; + + sen5xstate.voc_state_time = vocTime; + sen5xstate.voc_state_valid = vocValid; + // Unpack state (8 bytes) + sen5xstate.voc_state_array = (((uint64_t)vocState[7]) << 56) | ((uint64_t)vocState[6] << 48) | + ((uint64_t)vocState[5] << 40) | ((uint64_t)vocState[4] << 32) | + ((uint64_t)vocState[3] << 24) | ((uint64_t)vocState[2] << 16) | + ((uint64_t)vocState[1] << 8) | ((uint64_t)vocState[0]); + } + + bool okay = false; + + LOG_INFO("%s: state write to %s", sensorName, sen5XStateFileName); + pb_ostream_t stream = {&writecb, static_cast(&file), meshtastic_SEN5XState_size}; + + if (!pb_encode(&stream, &meshtastic_SEN5XState_msg, &sen5xstate)) { + LOG_ERROR("Error: can't encode protobuf %s", PB_GET_ERROR(&stream)); + } else { + okay = true; + } + + okay &= file.close(); + + if (okay) + LOG_INFO("%s: state write to %s successful", sensorName, sen5XStateFileName); + + return okay; +#else + LOG_ERROR("%s: ERROR - Filesystem not implemented", sensorName); +#endif +} + +bool SEN5XSensor::isActive() +{ + return state == SEN5X_MEASUREMENT || state == SEN5X_MEASUREMENT_2; +} + +uint32_t SEN5XSensor::wakeUp() +{ + + LOG_DEBUG("SEN5X: Waking up sensor"); + + if (!sendCommand(SEN5X_START_MEASUREMENT)) { + LOG_ERROR("SEN5X: Error starting measurement"); + // TODO - what should this return?? Something actually on the default interval? + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + delay(50); // From Sensirion Datasheet + + // TODO - This is currently "problematic" + // If time is updated in between reads, there is no way to + // keep track of how long it has passed + pmMeasureStarted = getTime(); + state = SEN5X_MEASUREMENT; + if (state == SEN5X_MEASUREMENT) + LOG_INFO("SEN5X: Started measurement mode"); + return SEN5X_WARMUP_MS_1; +} + +bool SEN5XSensor::vocStateStable() +{ + uint32_t now; + now = getTime(); + uint32_t sinceFirstMeasureStarted = (now - rhtGasMeasureStarted); + LOG_DEBUG("sinceFirstMeasureStarted: %us", sinceFirstMeasureStarted); + return sinceFirstMeasureStarted > SEN5X_VOC_STATE_WARMUP_S; +} + +bool SEN5XSensor::startCleaning() +{ + // Note: we only should enter here if we have a valid RTC with at least + // RTCQuality::RTCQualityDevice + state = SEN5X_CLEANING; + + // Note that cleaning command can only be run when the sensor is in measurement mode + if (!sendCommand(SEN5X_START_MEASUREMENT)) { + LOG_ERROR("SEN5X: Error starting measurment mode"); + return false; + } + delay(50); // From Sensirion Datasheet + + if (!sendCommand(SEN5X_START_FAN_CLEANING)) { + LOG_ERROR("SEN5X: Error starting fan cleaning"); + return false; + } + delay(20); // From Sensirion Datasheet + + // This message will be always printed so the user knows the device it's not hung + LOG_INFO("SEN5X: Started fan cleaning it will take 10 seconds..."); + + uint16_t started = millis(); + while (millis() - started < 10500) { + delay(500); + } + LOG_INFO("SEN5X: Cleaning done!!"); + + // Save timestamp in flash so we know when a week has passed + uint32_t now; + now = getValidTime(RTCQuality::RTCQualityDevice); + // If time is not RTCQualityNone, it will return non-zero + lastCleaning = now; + lastCleaningValid = true; + saveState(); + + idle(); + return true; +} + +bool SEN5XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + state = SEN5X_NOT_DETECTED; + LOG_INFO("Init sensor: %s", sensorName); + + _bus = bus; + _address = dev->address.address; + + delay(50); // without this there is an error on the deviceReset function + + if (!sendCommand(SEN5X_RESET)) { + LOG_ERROR("SEN5X: Error reseting device"); + return false; + } + delay(200); // From Sensirion Datasheet + + if (!findModel()) { + LOG_ERROR("SEN5X: error finding sensor model"); + return false; + } + + // Check the firmware version + if (!getVersion()) + return false; + if (firmwareVer < 2) { + LOG_ERROR("SEN5X: error firmware is too old and will not work with this implementation"); + return false; + } + delay(200); // From Sensirion Datasheet + + // Detection succeeded + state = SEN5X_IDLE; + status = 1; + + // Load state + loadState(); + + // Check if it is time to do a cleaning + uint32_t now; + int32_t passed; + now = getValidTime(RTCQuality::RTCQualityDevice); + + // If time is not RTCQualityNone, it will return non-zero + if (now) { + if (lastCleaningValid) { + + passed = now - lastCleaning; // in seconds + + if (passed > ONE_WEEK_IN_SECONDS && (now > SEN5X_VOC_VALID_DATE)) { + // If current date greater than 01/01/2018 (validity check) + LOG_INFO("SEN5X: More than a week (%us) since last cleaning in epoch (%us). Trigger, cleaning...", passed, + lastCleaning); + startCleaning(); + } else { + LOG_INFO("SEN5X: Cleaning not needed (%ds passed). Last cleaning date (in epoch): %us", passed, lastCleaning); + } + } else { + // We assume the device has just been updated or it is new, + // so no need to trigger a cleaning. + // Just save the timestamp to do a cleaning one week from now. + // Otherwise, we will never trigger cleaning in some cases + lastCleaning = now; + lastCleaningValid = true; + LOG_INFO("SEN5X: No valid last cleaning date found, saving it now: %us", lastCleaning); + saveState(); + } + + if (model != SEN50) { + if (!vocValid) { + LOG_INFO("SEN5X: No valid VOC's state found"); + } else { + // Check if state is recent + if (vocStateRecent(now)) { + // If current date greater than 01/01/2018 (validity check) + // Send it to the sensor + LOG_INFO("SEN5X: VOC state is valid and recent"); + vocStateToSensor(); + } else { + LOG_INFO("SEN5X: VOC state is too old or date is invalid"); + LOG_DEBUG("SEN5X: vocTime %u, Passed %u, and now %u", vocTime, passed, now); + } + } + } + } else { + // TODO - Should this actually ignore? We could end up never cleaning... + LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved state. Trying again later"); + } + + idle(false); + rhtGasMeasureStarted = now; + + initI2CSensor(); + return true; +} + +bool SEN5XSensor::readValues() +{ + if (!sendCommand(SEN5X_READ_VALUES)) { + LOG_ERROR("SEN5X: Error sending read command"); + return false; + } + LOG_DEBUG("SEN5X: Reading PM Values"); + delay(20); // From Sensirion Datasheet + + uint8_t dataBuffer[16]; + size_t receivedNumber = readBuffer(&dataBuffer[0], 24); + if (receivedNumber == 0) { + LOG_ERROR("SEN5X: Error getting values"); + return false; + } + + // Get the integers + uint16_t uint_pM1p0 = static_cast((dataBuffer[0] << 8) | dataBuffer[1]); + uint16_t uint_pM2p5 = static_cast((dataBuffer[2] << 8) | dataBuffer[3]); + uint16_t uint_pM4p0 = static_cast((dataBuffer[4] << 8) | dataBuffer[5]); + uint16_t uint_pM10p0 = static_cast((dataBuffer[6] << 8) | dataBuffer[7]); + + int16_t int_humidity = static_cast((dataBuffer[8] << 8) | dataBuffer[9]); + int16_t int_temperature = static_cast((dataBuffer[10] << 8) | dataBuffer[11]); + int16_t int_vocIndex = static_cast((dataBuffer[12] << 8) | dataBuffer[13]); + int16_t int_noxIndex = static_cast((dataBuffer[14] << 8) | dataBuffer[15]); + + // Convert values based on Sensirion Arduino lib + sen5xmeasurement.pM1p0 = !isnan(uint_pM1p0) ? uint_pM1p0 / 10 : UINT16_MAX; + sen5xmeasurement.pM2p5 = !isnan(uint_pM2p5) ? uint_pM2p5 / 10 : UINT16_MAX; + sen5xmeasurement.pM4p0 = !isnan(uint_pM4p0) ? uint_pM4p0 / 10 : UINT16_MAX; + sen5xmeasurement.pM10p0 = !isnan(uint_pM10p0) ? uint_pM10p0 / 10 : UINT16_MAX; + sen5xmeasurement.humidity = !isnan(int_humidity) ? int_humidity / 100.0f : FLT_MAX; + sen5xmeasurement.temperature = !isnan(int_temperature) ? int_temperature / 200.0f : FLT_MAX; + sen5xmeasurement.vocIndex = !isnan(int_vocIndex) ? int_vocIndex / 10.0f : FLT_MAX; + sen5xmeasurement.noxIndex = !isnan(int_noxIndex) ? int_noxIndex / 10.0f : FLT_MAX; + + LOG_DEBUG("Got: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sen5xmeasurement.pM1p0, sen5xmeasurement.pM2p5, + sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0); + + if (model != SEN50) { + LOG_DEBUG("Got: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sen5xmeasurement.humidity, sen5xmeasurement.temperature, + sen5xmeasurement.vocIndex); + } + + if (model == SEN55) { + LOG_DEBUG("Got: noxIndex=%.2f", sen5xmeasurement.noxIndex); + } + + return true; +} + +bool SEN5XSensor::readPNValues(bool cumulative) +{ + if (!sendCommand(SEN5X_READ_PM_VALUES)) { + LOG_ERROR("SEN5X: Error sending read command"); + return false; + } + + LOG_DEBUG("SEN5X: Reading PN Values"); + delay(20); // From Sensirion Datasheet + + uint8_t dataBuffer[20]; + size_t receivedNumber = readBuffer(&dataBuffer[0], 30); + if (receivedNumber == 0) { + LOG_ERROR("SEN5X: Error getting PN values"); + return false; + } + + // Get the integers + // uint16_t uint_pM1p0 = static_cast((dataBuffer[0] << 8) | dataBuffer[1]); + // uint16_t uint_pM2p5 = static_cast((dataBuffer[2] << 8) | dataBuffer[3]); + // uint16_t uint_pM4p0 = static_cast((dataBuffer[4] << 8) | dataBuffer[5]); + // uint16_t uint_pM10p0 = static_cast((dataBuffer[6] << 8) | dataBuffer[7]); + uint16_t uint_pN0p5 = static_cast((dataBuffer[8] << 8) | dataBuffer[9]); + uint16_t uint_pN1p0 = static_cast((dataBuffer[10] << 8) | dataBuffer[11]); + uint16_t uint_pN2p5 = static_cast((dataBuffer[12] << 8) | dataBuffer[13]); + uint16_t uint_pN4p0 = static_cast((dataBuffer[14] << 8) | dataBuffer[15]); + uint16_t uint_pN10p0 = static_cast((dataBuffer[16] << 8) | dataBuffer[17]); + uint16_t uint_tSize = static_cast((dataBuffer[18] << 8) | dataBuffer[19]); + + // Convert values based on Sensirion Arduino lib + // Multiply by 100 for converting from #/cm3 to #/0.1l for PN values + sen5xmeasurement.pN0p5 = !isnan(uint_pN0p5) ? uint_pN0p5 / 10 * 100 : UINT32_MAX; + sen5xmeasurement.pN1p0 = !isnan(uint_pN1p0) ? uint_pN1p0 / 10 * 100 : UINT32_MAX; + sen5xmeasurement.pN2p5 = !isnan(uint_pN2p5) ? uint_pN2p5 / 10 * 100 : UINT32_MAX; + sen5xmeasurement.pN4p0 = !isnan(uint_pN4p0) ? uint_pN4p0 / 10 * 100 : UINT32_MAX; + sen5xmeasurement.pN10p0 = !isnan(uint_pN10p0) ? uint_pN10p0 / 10 * 100 : UINT32_MAX; + sen5xmeasurement.tSize = !isnan(uint_tSize) ? uint_tSize / 1000.0f : FLT_MAX; + + // Remove accumuluative values: + // https://github.com/fablabbcn/smartcitizen-kit-2x/issues/85 + if (!cumulative) { + sen5xmeasurement.pN10p0 -= sen5xmeasurement.pN4p0; + sen5xmeasurement.pN4p0 -= sen5xmeasurement.pN2p5; + sen5xmeasurement.pN2p5 -= sen5xmeasurement.pN1p0; + sen5xmeasurement.pN1p0 -= sen5xmeasurement.pN0p5; + } + + LOG_DEBUG("Got: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f", sen5xmeasurement.pN0p5, + sen5xmeasurement.pN1p0, sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0, sen5xmeasurement.pN10p0, + sen5xmeasurement.tSize); + + return true; +} + +uint8_t SEN5XSensor::getMeasurements() +{ + uint32_t now; + now = getTime(); + + // Try to get new data + if (!sendCommand(SEN5X_READ_DATA_READY)) { + LOG_ERROR("SEN5X: Error sending command data ready flag"); + return 2; + } + delay(20); // From Sensirion Datasheet + + uint8_t dataReadyBuffer[3]; + size_t charNumber = readBuffer(&dataReadyBuffer[0], 3); + if (charNumber == 0) { + LOG_ERROR("SEN5X: Error getting device version value"); + return 2; + } + + bool dataReady = dataReadyBuffer[1]; + uint32_t sinceLastDataPollMs = (now - lastDataPoll) * 1000; + // Check if data is ready, and if since last time we requested is less than SEN5X_POLL_INTERVAL + if (!dataReady && (sinceLastDataPollMs > SEN5X_POLL_INTERVAL)) { + LOG_INFO("SEN5X: Data is not ready"); + return 1; + } + + if (!readValues()) { + LOG_ERROR("SEN5X: Error getting readings"); + return 2; + } + + if (!readPNValues(false)) { + LOG_ERROR("SEN5X: Error getting PN readings"); + return 2; + } + + lastDataPoll = now; + + return 0; +} + +int32_t SEN5XSensor::wakeUpTimeMs() +{ + return SEN5X_WARMUP_MS_2; +} + +int32_t SEN5XSensor::pendingForReadyMs() +{ + uint32_t now; + now = getTime(); + uint32_t sincePmMeasureStarted = (now - pmMeasureStarted) * 1000; + LOG_DEBUG("SEN5X: Since measure started: %ums", sincePmMeasureStarted); + + switch (state) { + case SEN5X_MEASUREMENT: { + + if (sincePmMeasureStarted < SEN5X_WARMUP_MS_1) { + LOG_INFO("SEN5X: not enough time passed since starting measurement"); + return SEN5X_WARMUP_MS_1 - sincePmMeasureStarted; + } + + if (!pmMeasureStarted) { + pmMeasureStarted = now; + } + + // Get PN values to check if we are above or below threshold + readPNValues(true); + lastDataPoll = now; + + // If the reading is low (the tyhreshold is in #/cm3) and second warmUp hasn't passed we return to come back later + if ((sen5xmeasurement.pN4p0 / 100) < SEN5X_PN4P0_CONC_THD && sincePmMeasureStarted < SEN5X_WARMUP_MS_2) { + LOG_INFO("SEN5X: Concentration is low, we will ask again in the second warm up period"); + state = SEN5X_MEASUREMENT_2; + // Report how many seconds are pending to cover the first warm up period + return SEN5X_WARMUP_MS_2 - sincePmMeasureStarted; + } + return 0; + } + case SEN5X_MEASUREMENT_2: { + if (sincePmMeasureStarted < SEN5X_WARMUP_MS_2) { + // Report how many seconds are pending to cover the first warm up period + return SEN5X_WARMUP_MS_2 - sincePmMeasureStarted; + } + return 0; + } + default: { + return -1; + } + } +} + +bool SEN5XSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_INFO("SEN5X: Attempting to get metrics"); + if (!isActive()) { + LOG_INFO("SEN5X: not in measurement mode"); + return false; + } + + uint8_t response; + response = getMeasurements(); + + if (response == 0) { + if (sen5xmeasurement.pM1p0 != UINT16_MAX) { + measurement->variant.air_quality_metrics.has_pm10_standard = true; + measurement->variant.air_quality_metrics.pm10_standard = sen5xmeasurement.pM1p0; + } + if (sen5xmeasurement.pM2p5 != UINT16_MAX) { + measurement->variant.air_quality_metrics.has_pm25_standard = true; + measurement->variant.air_quality_metrics.pm25_standard = sen5xmeasurement.pM2p5; + } + if (sen5xmeasurement.pM4p0 != UINT16_MAX) { + measurement->variant.air_quality_metrics.has_pm40_standard = true; + measurement->variant.air_quality_metrics.pm40_standard = sen5xmeasurement.pM4p0; + } + if (sen5xmeasurement.pM10p0 != UINT16_MAX) { + measurement->variant.air_quality_metrics.has_pm100_standard = true; + measurement->variant.air_quality_metrics.pm100_standard = sen5xmeasurement.pM10p0; + } + if (sen5xmeasurement.pN0p5 != UINT32_MAX) { + measurement->variant.air_quality_metrics.has_particles_05um = true; + measurement->variant.air_quality_metrics.particles_05um = sen5xmeasurement.pN0p5; + } + if (sen5xmeasurement.pN1p0 != UINT32_MAX) { + measurement->variant.air_quality_metrics.has_particles_10um = true; + measurement->variant.air_quality_metrics.particles_10um = sen5xmeasurement.pN1p0; + } + if (sen5xmeasurement.pN2p5 != UINT32_MAX) { + measurement->variant.air_quality_metrics.has_particles_25um = true; + measurement->variant.air_quality_metrics.particles_25um = sen5xmeasurement.pN2p5; + } + if (sen5xmeasurement.pN4p0 != UINT32_MAX) { + measurement->variant.air_quality_metrics.has_particles_40um = true; + measurement->variant.air_quality_metrics.particles_40um = sen5xmeasurement.pN4p0; + } + if (sen5xmeasurement.pN10p0 != UINT32_MAX) { + measurement->variant.air_quality_metrics.has_particles_100um = true; + measurement->variant.air_quality_metrics.particles_100um = sen5xmeasurement.pN10p0; + } + if (sen5xmeasurement.tSize != FLT_MAX) { + measurement->variant.air_quality_metrics.has_particles_tps = true; + measurement->variant.air_quality_metrics.particles_tps = sen5xmeasurement.tSize; + } + + if (model != SEN50) { + if (sen5xmeasurement.humidity != FLT_MAX) { + measurement->variant.air_quality_metrics.has_pm_humidity = true; + measurement->variant.air_quality_metrics.pm_humidity = sen5xmeasurement.humidity; + } + if (sen5xmeasurement.temperature != FLT_MAX) { + measurement->variant.air_quality_metrics.has_pm_temperature = true; + measurement->variant.air_quality_metrics.pm_temperature = sen5xmeasurement.temperature; + } + if (sen5xmeasurement.noxIndex != FLT_MAX) { + measurement->variant.air_quality_metrics.has_pm_voc_idx = true; + measurement->variant.air_quality_metrics.pm_voc_idx = sen5xmeasurement.vocIndex; + } + } + + if (model == SEN55) { + if (sen5xmeasurement.noxIndex != FLT_MAX) { + measurement->variant.air_quality_metrics.has_pm_nox_idx = true; + measurement->variant.air_quality_metrics.pm_nox_idx = sen5xmeasurement.noxIndex; + } + } + + return true; + } else if (response == 1) { + // TODO return because data was not ready yet + // Should this return false? + idle(); + return false; + } else if (response == 2) { + // Return with error for non-existing data + idle(); + return false; + } + + return true; +} + +void SEN5XSensor::setMode(bool setOneShot) +{ + oneShotMode = setOneShot; +} + +AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) +{ + AdminMessageHandleResult result; + result = AdminMessageHandleResult::NOT_HANDLED; + + switch (request->which_payload_variant) { + case meshtastic_AdminMessage_sensor_config_tag: + if (!request->sensor_config.has_sen5x_config) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } + + // TODO - Add admin command to set temperature offset + // Check for temperature offset + // if (request->sensor_config.sen5x_config.has_set_temperature) { + // this->setTemperature(request->sensor_config.sen5x_config.set_temperature); + // } + + // Check for one-shot/continuous mode request + if (request->sensor_config.sen5x_config.has_set_one_shot_mode) { + this->setMode(request->sensor_config.sen5x_config.set_one_shot_mode); + } + + result = AdminMessageHandleResult::HANDLED; + break; + + default: + result = AdminMessageHandleResult::NOT_HANDLED; + } + + return result; +} +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.h b/src/modules/Telemetry/Sensor/SEN5XSensor.h new file mode 100644 index 000000000..46f8c70e9 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.h @@ -0,0 +1,170 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "RTC.h" +#include "TelemetrySensor.h" +#include "Wire.h" + +// Warm up times for SEN5X from the datasheet +#ifndef SEN5X_WARMUP_MS_1 +#define SEN5X_WARMUP_MS_1 15000 +#endif + +#ifndef SEN5X_WARMUP_MS_2 +#define SEN5X_WARMUP_MS_2 30000 +#endif + +#ifndef SEN5X_POLL_INTERVAL +#define SEN5X_POLL_INTERVAL 1000 +#endif + +#ifndef SEN5X_I2C_CLOCK_SPEED +#define SEN5X_I2C_CLOCK_SPEED 100000 +#endif + +/* +Time after which the sensor can go to sleep, as the warmup period has passed +and the VOCs sensor will is allowed to stop (although needs to recover the state +each time) +*/ +#ifndef SEN5X_VOC_STATE_WARMUP_S +/* Note for Testing 5' is enough +Sensirion recommends 1h +This can be bypassed completely if switching to low-power RHT/Gas mode and setting +SEN5X_VOC_STATE_WARMUP_S 0 +*/ +#define SEN5X_VOC_STATE_WARMUP_S 3600 +#endif + +#define ONE_WEEK_IN_SECONDS 604800 + +struct _SEN5XMeasurements { + uint16_t pM1p0; + uint16_t pM2p5; + uint16_t pM4p0; + uint16_t pM10p0; + uint32_t pN0p5; + uint32_t pN1p0; + uint32_t pN2p5; + uint32_t pN4p0; + uint32_t pN10p0; + float tSize; + float humidity; + float temperature; + float vocIndex; + float noxIndex; +}; + +class SEN5XSensor : public TelemetrySensor +{ + private: + TwoWire *_bus{}; + uint8_t _address{}; + + bool getVersion(); + float firmwareVer = -1; + float hardwareVer = -1; + float protocolVer = -1; + bool findModel(); + +// Commands +#define SEN5X_RESET 0xD304 +#define SEN5X_GET_PRODUCT_NAME 0xD014 +#define SEN5X_GET_FIRMWARE_VERSION 0xD100 +#define SEN5X_START_MEASUREMENT 0x0021 +#define SEN5X_START_MEASUREMENT_RHT_GAS 0x0037 +#define SEN5X_STOP_MEASUREMENT 0x0104 +#define SEN5X_READ_DATA_READY 0x0202 +#define SEN5X_START_FAN_CLEANING 0x5607 +#define SEN5X_RW_VOCS_STATE 0x6181 + +#define SEN5X_READ_VALUES 0x03C4 +#define SEN5X_READ_RAW_VALUES 0x03D2 +#define SEN5X_READ_PM_VALUES 0x0413 + +#define SEN5X_VOC_VALID_TIME 600 +#define SEN5X_VOC_VALID_DATE 1514764800 + + enum SEN5Xmodel { SEN5X_UNKNOWN = 0, SEN50 = 0b001, SEN54 = 0b010, SEN55 = 0b100 }; + SEN5Xmodel model = SEN5X_UNKNOWN; + + enum SEN5XState { + SEN5X_OFF, + SEN5X_IDLE, + SEN5X_RHTGAS_ONLY, + SEN5X_MEASUREMENT, + SEN5X_MEASUREMENT_2, + SEN5X_CLEANING, + SEN5X_NOT_DETECTED + }; + SEN5XState state = SEN5X_OFF; + // Flag to work on one-shot (read and sleep), or continuous mode + bool oneShotMode = true; + void setMode(bool setOneShot); + bool vocStateValid(); +/* Sensirion recommends taking a reading after 15 seconds, +if the Particle number reading is over 100#/cm3 the reading is OK, +but if it is lower wait until 30 seconds and take it again. +See: https://sensirion.com/resource/application_note/low_power_mode/sen5x +*/ +#define SEN5X_PN4P0_CONC_THD 100 + + bool sendCommand(uint16_t command); + bool sendCommand(uint16_t command, uint8_t *buffer, uint8_t byteNumber = 0); + uint8_t readBuffer(uint8_t *buffer, uint8_t byteNumber); // Return number of bytes received + uint8_t sen5xCRC(uint8_t *buffer); + bool startCleaning(); + uint8_t getMeasurements(); + // bool readRawValues(); + bool readPNValues(bool cumulative); + bool readValues(); + + uint32_t pmMeasureStarted = 0; + uint32_t rhtGasMeasureStarted = 0; + uint32_t lastDataPoll = 0; + _SEN5XMeasurements sen5xmeasurement{}; + + bool idle(bool checkState = true); + + protected: + // Store status of the sensor in this file + const char *sen5XStateFileName = "/prefs/sen5X.dat"; + meshtastic_SEN5XState sen5xstate = meshtastic_SEN5XState_init_zero; + + bool loadState(); + bool saveState(); + + // Cleaning State + uint32_t lastCleaning = 0; + bool lastCleaningValid = false; + +// VOC State +#define SEN5X_VOC_STATE_BUFFER_SIZE 8 + uint8_t vocState[SEN5X_VOC_STATE_BUFFER_SIZE]{}; + uint32_t vocTime = 0; + bool vocValid = false; + + bool vocStateFromSensor(); + bool vocStateToSensor(); + bool vocStateStable(); + bool vocStateRecent(uint32_t now); + + public: + SEN5XSensor(); + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + + virtual bool isActive() override; + virtual void sleep() override; + virtual uint32_t wakeUp() override; + virtual bool canSleep() override { return true; } + virtual int32_t wakeUpTimeMs() override; + virtual int32_t pendingForReadyMs() override; + + AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) override; +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index af51ddfad..6c53bbd72 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -26,7 +26,6 @@ class TelemetrySensor this->status = 0; } - const char *sensorName; meshtastic_TelemetrySensorType sensorType = meshtastic_TelemetrySensorType_SENSOR_UNSET; unsigned status; bool initialized = false; @@ -56,13 +55,18 @@ class TelemetrySensor return AdminMessageHandleResult::NOT_HANDLED; } + const char *sensorName; // TODO: delete after migration bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; } + // Functions to sleep / wakeup sensors that support it + // These functions can save power consumption in cases like AQ virtual void sleep(){}; virtual uint32_t wakeUp() { return 0; } - // Return active by default, override per sensor - virtual bool isActive() { return true; } + virtual bool isActive() { return true; } // Return true by default, override per sensor + virtual bool canSleep() { return false; } // Return false by default, override per sensor + virtual int32_t wakeUpTimeMs() { return 0; } + virtual int32_t pendingForReadyMs() { return 0; } #if WIRE_INTERFACES_COUNT > 1 // Set to true if Implementation only works first I2C port (Wire) diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index a12972cb0..92e700368 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -149,18 +149,18 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.air_quality_metrics.has_pm100_standard) { msgPayload["pm100"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_standard); } - if (decoded->variant.air_quality_metrics.has_pm10_environmental) { - msgPayload["pm10_e"] = - new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_environmental); - } - if (decoded->variant.air_quality_metrics.has_pm25_environmental) { - msgPayload["pm25_e"] = - new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_environmental); - } - if (decoded->variant.air_quality_metrics.has_pm100_environmental) { - msgPayload["pm100_e"] = - new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_environmental); - } + // if (decoded->variant.air_quality_metrics.has_pm10_environmental) { + // msgPayload["pm10_e"] = + // new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_environmental); + // } + // if (decoded->variant.air_quality_metrics.has_pm25_environmental) { + // msgPayload["pm25_e"] = + // new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_environmental); + // } + // if (decoded->variant.air_quality_metrics.has_pm100_environmental) { + // msgPayload["pm100_e"] = + // new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_environmental); + // } } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { if (decoded->variant.power_metrics.has_ch1_voltage) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index 41f505b94..80aef0e94 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -120,15 +120,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.air_quality_metrics.has_pm100_standard) { jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; } - if (decoded->variant.air_quality_metrics.has_pm10_environmental) { - jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; - } - if (decoded->variant.air_quality_metrics.has_pm25_environmental) { - jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; - } - if (decoded->variant.air_quality_metrics.has_pm100_environmental) { - jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; - } + // if (decoded->variant.air_quality_metrics.has_pm10_environmental) { + // jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; + // } + // if (decoded->variant.air_quality_metrics.has_pm25_environmental) { + // jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; + // } + // if (decoded->variant.air_quality_metrics.has_pm100_environmental) { + // jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; + // } } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { if (decoded->variant.power_metrics.has_ch1_voltage) { jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; diff --git a/variants/stm32/wio-e5/platformio.ini b/variants/stm32/wio-e5/platformio.ini index 311cade58..c8dbb2b72 100644 --- a/variants/stm32/wio-e5/platformio.ini +++ b/variants/stm32/wio-e5/platformio.ini @@ -17,5 +17,6 @@ build_flags = -DPIN_SERIAL2_RX=PA3 -DHAS_GPS=1 -DGPS_SERIAL_PORT=Serial2 + -DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1 upload_port = stlink From bb3d6d5326518b36598ff0a0b8f4b334c953e0fe Mon Sep 17 00:00:00 2001 From: Nick <79813408+niccellular@users.noreply.github.com> Date: Sun, 8 Feb 2026 12:20:15 -0500 Subject: [PATCH 106/223] Fix embedded null byte truncation in ATAK strings (#9570) --- src/meshUtils.cpp | 11 ++ src/meshUtils.h | 3 + src/modules/AtakPluginModule.cpp | 69 +++++----- test/TestUtil.cpp | 16 +++ test/TestUtil.h | 5 +- test/test_atak/test_main.cpp | 216 +++++++++++++++++++++++++++++++ 6 files changed, 289 insertions(+), 31 deletions(-) create mode 100644 test/test_atak/test_main.cpp diff --git a/src/meshUtils.cpp b/src/meshUtils.cpp index ea2ba641b..1a4497101 100644 --- a/src/meshUtils.cpp +++ b/src/meshUtils.cpp @@ -106,4 +106,15 @@ const std::string vformat(const char *const zcFormat, ...) std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs); va_end(vaArgs); return std::string(zc.data(), iLen); +} + +size_t pb_string_length(const char *str, size_t max_len) +{ + size_t len = 0; + for (size_t i = 0; i < max_len; i++) { + if (str[i] != '\0') { + len = i + 1; + } + } + return len; } \ No newline at end of file diff --git a/src/meshUtils.h b/src/meshUtils.h index 9fcf6f8a8..67446f91f 100644 --- a/src/meshUtils.h +++ b/src/meshUtils.h @@ -35,4 +35,7 @@ bool isOneOf(int item, int count, ...); const std::string vformat(const char *const zcFormat, ...); +// Get actual string length for nanopb char array fields. +size_t pb_string_length(const char *str, size_t max_len); + #define IS_ONE_OF(item, ...) isOneOf(item, sizeof((int[]){__VA_ARGS__}) / sizeof(int), __VA_ARGS__) diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp index a51ef54c3..bddb6276b 100644 --- a/src/modules/AtakPluginModule.cpp +++ b/src/modules/AtakPluginModule.cpp @@ -6,6 +6,7 @@ #include "configuration.h" #include "main.h" #include "mesh/compression/unishox2.h" +#include "meshUtils.h" #include "meshtastic/atak.pb.h" AtakPluginModule *atakPluginModule; @@ -70,16 +71,17 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast auto compressed = cloneTAKPacketData(t); compressed.is_compressed = true; if (t->has_contact) { - auto length = unishox2_compress_lines(t->contact.callsign, strlen(t->contact.callsign), compressed.contact.callsign, - sizeof(compressed.contact.callsign) - 1, USX_PSET_DFLT, NULL); + auto length = unishox2_compress_lines( + t->contact.callsign, pb_string_length(t->contact.callsign, sizeof(t->contact.callsign)), + compressed.contact.callsign, sizeof(compressed.contact.callsign) - 1, USX_PSET_DFLT, NULL); if (length < 0) { LOG_WARN("Compress overflow contact.callsign. Revert to uncompressed packet"); return; } LOG_DEBUG("Compressed callsign: %d bytes", length); - length = unishox2_compress_lines(t->contact.device_callsign, strlen(t->contact.device_callsign), - compressed.contact.device_callsign, sizeof(compressed.contact.device_callsign) - 1, - USX_PSET_DFLT, NULL); + length = unishox2_compress_lines( + t->contact.device_callsign, pb_string_length(t->contact.device_callsign, sizeof(t->contact.device_callsign)), + compressed.contact.device_callsign, sizeof(compressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); if (length < 0) { LOG_WARN("Compress overflow contact.device_callsign. Revert to uncompressed packet"); return; @@ -87,9 +89,11 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast LOG_DEBUG("Compressed device_callsign: %d bytes", length); } if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) { - auto length = unishox2_compress_lines(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), - compressed.payload_variant.chat.message, - sizeof(compressed.payload_variant.chat.message) - 1, USX_PSET_DFLT, NULL); + auto length = unishox2_compress_lines( + t->payload_variant.chat.message, + pb_string_length(t->payload_variant.chat.message, sizeof(t->payload_variant.chat.message)), + compressed.payload_variant.chat.message, sizeof(compressed.payload_variant.chat.message) - 1, USX_PSET_DFLT, + NULL); if (length < 0) { LOG_WARN("Compress overflow chat.message. Revert to uncompressed packet"); return; @@ -98,9 +102,9 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast if (t->payload_variant.chat.has_to) { compressed.payload_variant.chat.has_to = true; - length = unishox2_compress_lines(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), - compressed.payload_variant.chat.to, - sizeof(compressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL); + length = unishox2_compress_lines( + t->payload_variant.chat.to, pb_string_length(t->payload_variant.chat.to, sizeof(t->payload_variant.chat.to)), + compressed.payload_variant.chat.to, sizeof(compressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL); if (length < 0) { LOG_WARN("Compress overflow chat.to. Revert to uncompressed packet"); return; @@ -110,9 +114,11 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast if (t->payload_variant.chat.has_to_callsign) { compressed.payload_variant.chat.has_to_callsign = true; - length = unishox2_compress_lines(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign), - compressed.payload_variant.chat.to_callsign, - sizeof(compressed.payload_variant.chat.to_callsign) - 1, USX_PSET_DFLT, NULL); + length = unishox2_compress_lines( + t->payload_variant.chat.to_callsign, + pb_string_length(t->payload_variant.chat.to_callsign, sizeof(t->payload_variant.chat.to_callsign)), + compressed.payload_variant.chat.to_callsign, sizeof(compressed.payload_variant.chat.to_callsign) - 1, + USX_PSET_DFLT, NULL); if (length < 0) { LOG_WARN("Compress overflow chat.to_callsign. Revert to uncompressed packet"); return; @@ -134,18 +140,18 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast auto uncompressed = cloneTAKPacketData(t); uncompressed.is_compressed = false; if (t->has_contact) { - auto length = - unishox2_decompress_lines(t->contact.callsign, strlen(t->contact.callsign), uncompressed.contact.callsign, - sizeof(uncompressed.contact.callsign) - 1, USX_PSET_DFLT, NULL); + auto length = unishox2_decompress_lines( + t->contact.callsign, pb_string_length(t->contact.callsign, sizeof(t->contact.callsign)), + uncompressed.contact.callsign, sizeof(uncompressed.contact.callsign) - 1, USX_PSET_DFLT, NULL); if (length < 0) { LOG_WARN("Decompress overflow contact.callsign. Bailing out"); return; } LOG_DEBUG("Decompressed callsign: %d bytes", length); - length = unishox2_decompress_lines(t->contact.device_callsign, strlen(t->contact.device_callsign), - uncompressed.contact.device_callsign, - sizeof(uncompressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); + length = unishox2_decompress_lines( + t->contact.device_callsign, pb_string_length(t->contact.device_callsign, sizeof(t->contact.device_callsign)), + uncompressed.contact.device_callsign, sizeof(uncompressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL); if (length < 0) { LOG_WARN("Decompress overflow contact.device_callsign. Bailing out"); return; @@ -153,9 +159,11 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast LOG_DEBUG("Decompressed device_callsign: %d bytes", length); } if (uncompressed.which_payload_variant == meshtastic_TAKPacket_chat_tag) { - auto length = unishox2_decompress_lines(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message), - uncompressed.payload_variant.chat.message, - sizeof(uncompressed.payload_variant.chat.message) - 1, USX_PSET_DFLT, NULL); + auto length = unishox2_decompress_lines( + t->payload_variant.chat.message, + pb_string_length(t->payload_variant.chat.message, sizeof(t->payload_variant.chat.message)), + uncompressed.payload_variant.chat.message, sizeof(uncompressed.payload_variant.chat.message) - 1, USX_PSET_DFLT, + NULL); if (length < 0) { LOG_WARN("Decompress overflow chat.message. Bailing out"); return; @@ -164,9 +172,9 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast if (t->payload_variant.chat.has_to) { uncompressed.payload_variant.chat.has_to = true; - length = unishox2_decompress_lines(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to), - uncompressed.payload_variant.chat.to, - sizeof(uncompressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL); + length = unishox2_decompress_lines( + t->payload_variant.chat.to, pb_string_length(t->payload_variant.chat.to, sizeof(t->payload_variant.chat.to)), + uncompressed.payload_variant.chat.to, sizeof(uncompressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL); if (length < 0) { LOG_WARN("Decompress overflow chat.to. Bailing out"); return; @@ -176,10 +184,11 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast if (t->payload_variant.chat.has_to_callsign) { uncompressed.payload_variant.chat.has_to_callsign = true; - length = - unishox2_decompress_lines(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign), - uncompressed.payload_variant.chat.to_callsign, - sizeof(uncompressed.payload_variant.chat.to_callsign) - 1, USX_PSET_DFLT, NULL); + length = unishox2_decompress_lines( + t->payload_variant.chat.to_callsign, + pb_string_length(t->payload_variant.chat.to_callsign, sizeof(t->payload_variant.chat.to_callsign)), + uncompressed.payload_variant.chat.to_callsign, sizeof(uncompressed.payload_variant.chat.to_callsign) - 1, + USX_PSET_DFLT, NULL); if (length < 0) { LOG_WARN("Decompress overflow chat.to_callsign. Bailing out"); return; diff --git a/test/TestUtil.cpp b/test/TestUtil.cpp index b470b8ce8..a8262238f 100644 --- a/test/TestUtil.cpp +++ b/test/TestUtil.cpp @@ -4,6 +4,13 @@ #include "TestUtil.h" +#if defined(ARDUINO) +#include +#else +#include +#include +#endif + void initializeTestEnvironment() { concurrency::hasBeenSetup = true; @@ -15,4 +22,13 @@ void initializeTestEnvironment() perhapsSetRTC(RTCQualityNTP, &tv); #endif concurrency::OSThread::setup(); +} + +void testDelay(unsigned long ms) +{ +#if defined(ARDUINO) + ::delay(ms); +#else + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +#endif } \ No newline at end of file diff --git a/test/TestUtil.h b/test/TestUtil.h index ce021e459..fb634184b 100644 --- a/test/TestUtil.h +++ b/test/TestUtil.h @@ -1,4 +1,7 @@ #pragma once // Initialize testing environment. -void initializeTestEnvironment(); \ No newline at end of file +void initializeTestEnvironment(); + +// Portable delay for tests (Arduino or host). +void testDelay(unsigned long ms); \ No newline at end of file diff --git a/test/test_atak/test_main.cpp b/test/test_atak/test_main.cpp new file mode 100644 index 000000000..84078b300 --- /dev/null +++ b/test/test_atak/test_main.cpp @@ -0,0 +1,216 @@ +#include +#include + +#include "TestUtil.h" +#include "meshUtils.h" + +void setUp(void) +{ + // set stuff up here +} + +void tearDown(void) +{ + // clean stuff up here +} + +/** + * Test normal string without embedded nulls + * Should behave the same as strlen() for regular strings + */ +void test_normal_string(void) +{ + char test_str[32] = "Hello World"; + size_t expected = 11; // strlen("Hello World") + size_t result = pb_string_length(test_str, sizeof(test_str)); + TEST_ASSERT_EQUAL_size_t(expected, result); +} + +/** + * Test empty string + * Should return 0 for empty string + */ +void test_empty_string(void) +{ + char test_str[32] = ""; + size_t expected = 0; + size_t result = pb_string_length(test_str, sizeof(test_str)); + TEST_ASSERT_EQUAL_size_t(expected, result); +} + +/** + * Test string with only trailing nulls + * Common case - string followed by null padding + */ +void test_trailing_nulls(void) +{ + char test_str[32] = {0}; + strcpy(test_str, "Test"); + // test_str is now: "Test\0\0\0\0..." (4 chars + 28 nulls) + size_t expected = 4; + size_t result = pb_string_length(test_str, sizeof(test_str)); + TEST_ASSERT_EQUAL_size_t(expected, result); +} + +/** + * Test string with embedded null byte + * This is the critical bug case - strlen() would truncate at first null + */ +void test_embedded_null(void) +{ + char test_str[32] = {0}; + // Create string "ABC\0XYZ" (embedded null after C) + test_str[0] = 'A'; + test_str[1] = 'B'; + test_str[2] = 'C'; + test_str[3] = '\0'; // embedded null + test_str[4] = 'X'; + test_str[5] = 'Y'; + test_str[6] = 'Z'; + // Rest is already null from initialization + + // strlen would return 3, but pb_string_length should return 7 + size_t strlen_result = strlen(test_str); + size_t pb_result = pb_string_length(test_str, sizeof(test_str)); + + TEST_ASSERT_EQUAL_size_t(3, strlen_result); // strlen stops at first null + TEST_ASSERT_EQUAL_size_t(7, pb_result); // pb_string_length finds last non-null +} + +/** + * Test Android UID with embedded null bytes + * Real-world case from bug report: ANDROID-e7e455b40002429d + * The "00" in the UID represents 0x00 bytes that were truncating the string + */ +void test_android_uid_pattern(void) +{ + char test_str[32] = {0}; + // Simulate "ANDROID-e7e455b4" + 0x00 + 0x00 + "2429d" + const char part1[] = "ANDROID-e7e455b4"; + strcpy(test_str, part1); + size_t pos = strlen(part1); + test_str[pos] = '\0'; // embedded null + test_str[pos + 1] = '\0'; // another embedded null + strcpy(test_str + pos + 2, "2429d"); + + // The full UID should be 24 characters + size_t strlen_result = strlen(test_str); + size_t pb_result = pb_string_length(test_str, sizeof(test_str)); + + TEST_ASSERT_EQUAL_size_t(16, strlen_result); // strlen truncates to "ANDROID-e7e455b4" + TEST_ASSERT_EQUAL_size_t(23, pb_result); // pb_string_length gets full length +} + +/** + * Test string with multiple embedded nulls + * Edge case with several null bytes scattered through the string + */ +void test_multiple_embedded_nulls(void) +{ + char test_str[32] = {0}; + // Create "A\0B\0C\0D" (3 embedded nulls) + test_str[0] = 'A'; + test_str[1] = '\0'; + test_str[2] = 'B'; + test_str[3] = '\0'; + test_str[4] = 'C'; + test_str[5] = '\0'; + test_str[6] = 'D'; + + size_t strlen_result = strlen(test_str); + size_t pb_result = pb_string_length(test_str, sizeof(test_str)); + + TEST_ASSERT_EQUAL_size_t(1, strlen_result); // strlen stops at first null + TEST_ASSERT_EQUAL_size_t(7, pb_result); // pb_string_length finds all chars +} + +/** + * Test buffer completely filled with non-null characters + * Edge case where string uses entire buffer + */ +void test_full_buffer(void) +{ + char test_str[8]; + // Fill entire buffer with 'X' + memset(test_str, 'X', sizeof(test_str)); + + size_t result = pb_string_length(test_str, sizeof(test_str)); + TEST_ASSERT_EQUAL_size_t(8, result); +} + +/** + * Test buffer with all nulls + * Should return 0 + */ +void test_all_nulls(void) +{ + char test_str[32] = {0}; + size_t result = pb_string_length(test_str, sizeof(test_str)); + TEST_ASSERT_EQUAL_size_t(0, result); +} + +/** + * Test single character followed by nulls + * Minimal non-empty case + */ +void test_single_char(void) +{ + char test_str[32] = {0}; + test_str[0] = 'X'; + + size_t result = pb_string_length(test_str, sizeof(test_str)); + TEST_ASSERT_EQUAL_size_t(1, result); +} + +/** + * Test callsign field typical size + * Test with typical ATAK callsign field size (64 bytes) + */ +void test_callsign_field_size(void) +{ + char test_str[64] = {0}; + strcpy(test_str, "CALLSIGN-123"); + + size_t result = pb_string_length(test_str, sizeof(test_str)); + TEST_ASSERT_EQUAL_size_t(12, result); +} + +/** + * Test with data at end of buffer + * String with embedded null and data at very end + */ +void test_data_at_buffer_end(void) +{ + char test_str[10] = {0}; + test_str[0] = 'A'; + test_str[1] = '\0'; + test_str[8] = 'Z'; // Data near end + test_str[9] = 'X'; // Data at end + + size_t result = pb_string_length(test_str, sizeof(test_str)); + TEST_ASSERT_EQUAL_size_t(10, result); // Should find the 'X' at position 9 +} + +void setup() +{ + // NOTE!!! Wait for >2 secs + // if board doesn't support software reset via Serial.DTR/RTS + testDelay(10); + testDelay(2000); + + UNITY_BEGIN(); + RUN_TEST(test_normal_string); + RUN_TEST(test_empty_string); + RUN_TEST(test_trailing_nulls); + RUN_TEST(test_embedded_null); + RUN_TEST(test_android_uid_pattern); + RUN_TEST(test_multiple_embedded_nulls); + RUN_TEST(test_full_buffer); + RUN_TEST(test_all_nulls); + RUN_TEST(test_single_char); + RUN_TEST(test_callsign_field_size); + RUN_TEST(test_data_at_buffer_end); + exit(UNITY_END()); +} + +void loop() {} From 73adeee3851bfe1cf73593eae68edccdbd31b991 Mon Sep 17 00:00:00 2001 From: Eric Sesterhenn Date: Mon, 9 Feb 2026 02:48:18 +0100 Subject: [PATCH 107/223] Enable FORTIFY and SP for native builds (#9537) * Enable FORITFY and NX for native builds meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack. This gives the following improvements on Debian Trixie: $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd Tested with --sim mode I do not get any crashes or similar. * Enable FORTIFY and NX for native builds meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack. This gives the following improvements on Debian Trixie: $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd Tested with --sim mode I do not get any crashes or similar. * Enable FORTIFY and SP for native builds meshtasticd does have a stack canaries and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and stack canaries. This gives the following improvements on Debian Trixie: $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd Tested with --sim mode I do not get any crashes or similar. --------- Co-authored-by: Ben Meadors --- variants/native/portduino.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index b86420291..6d6b73df5 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -41,6 +41,8 @@ build_flags = ${arduino_base.build_flags} -D ARCH_PORTDUINO -fPIC + -D_FORTIFY_SOURCE=2 + -fstack-protector-all -Wstack-protector --param ssp-buffer-size=4 -Isrc/platform/portduino -DRADIOLIB_EEPROM_UNSUPPORTED -DPORTDUINO_LINUX_HARDWARE From 579a0af5163512632a3cba47dc6781e97a808241 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Mon, 9 Feb 2026 15:39:12 +0100 Subject: [PATCH 108/223] feat: Add SCD4X (#7198) * Move PMSA003I to separate class and update AQ telemetry * AirQualityTelemetry module not depend on PM sensor presence * Remove commented line * Fixes on PMS class * Add missing warmup period to wakeUp function * Fixes on compilation for different variants * Add functions to check for I2C bus speed and set it * Add ScreenFonts.h Co-authored-by: Hannes Fuchs * PMSA003I 1st round test * Fix I2C scan speed * Fix minor issues and bring back I2C SPEED def * Remove PMSA003I library as its no longer needed * Add functional SCD4X * Fix screen frame for CO2 * Add admin commands to SCD4X class * Add further admin commands and fixes. * Remove unused I2C speed functions and cleanup * Cleanup of SEN5X specific code added from switching branches * Remove SCAN_I2C_CLOCK_SPEED block as its not needed * Remove associated functions for setting I2C speed * Unify build epoch to add flag in platformio-custom.py (#7917) * Unify build_epoch replacement logic in platformio-custom * Missed one * Fix build error in rak_wismesh_tap_v2 (#7905) In the logs was: "No screen resolution defined in build_flags. Please define DISPLAY_SIZE." set according to similar devices. * Put guards in place around debug heap operations (#7955) * Put guards in place around debug heap operations * Add macros to clean up code * Add pointer as well * Cleanup * Fix memory leak in NextHopRouter: always free packet copy when removing from pending * Formatting * Only queue 2 client notification * Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap * Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap * Update protobufs (#7973) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs * Trunk * Trunk * Static memory pool allocation (#7966) * Static memory pool * Initializer * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs --------- Co-authored-by: WillyJL * Portduino dynamic alloc * Missed * Drop the limit * Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix json report crashes on esp32 (#7978) * Tweak maximums * Fix DRAM overflow on old esp32 targets * Guard bad time warning logs using GPS_DEBUG (#7897) In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy. In combination, these result in a spamming of the logs when a bad time is found When the GPS is active, we're calling the GPS thread every 0.2secs. So this log could be printed 4,500 times in a no-lock scenario :) Reserve this experience for developers using GPS_DEBUG. Fixes https://github.com/meshtastic/firmware/issues/7896 * Scale probe buffer size based on current baud rate (#7975) * Scale probe buffer size based on current baud rate * Throttle bad time validation logging and fix time comparison logic * Remove comment * Missed the other instances * Copy pasta * Fix GPS gm_mktime memory leak (#7981) * Fix overflow of time value (#7984) * Fix overflow of time value * Revert "Fix overflow of time value" This reverts commit 084796920179e80a7500d36c25fd4d82b3ef4214. * That got boogered up * Remove PMSA003 include from modules * Add flag to exclude air quality module * Rework PMSA003I to align with new I2C scanner * Reworks AQ telemetry to match new dynamic allocation method * Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements * Reworks PMSA003I * Move add sensor template to separate file * Split telemetry on screen options * Add variable I2C clock compile flag * Added to Seeed Xiao S3 as demo * Fix drawFrame in AQ module * Module settings override to i2cScan module function * Move to CAN_RECLOCK_I2C per architecture * Add reclock function in TelemetrySensor.cpp * Add flag in ESP32 common * Minor fix * Move I2C reclock function to src/detect * Fix uninitMemberVar errors and compile issue * Make sleep, wakeUp functions generic * Fix STM32 builds * Add exclude AQ sensor to builds that have environmental sensor excludes * Add includes to AddI2CSensorTemplate.h * SEN5X first pass * WIP Sen5X functions * Further (non-working) progress in SEN5X * WIP Sen5X functions * Changes on SEN5X library - removing pm_env as well * Small cleanup of SEN5X sensors * Minor change for SEN5X detection * Remove dup code * Enable PM sensor before sending telemetry. This enables the PM sensor for a predefined period to allow for warmup. Once telemetry is sent, the sensor shuts down again. * Small cleanups in SEN5X sensor * Add dynamic measurement interval for SEN5X * Only disable SEN5X if enough time after reading. * Idle for SEN5X on communication error * Cleanup of logs and remove unnecessary delays * Small TODO * Settle on uint16_t for SEN5X PM data * Make AQTelemetry sensors non-exclusive * Implementation of cleaning in FS prefs and cleanup * Remove unnecessary LOGS * Add cleaning date storage in FS * Report non-cumulative PN * Bring back detection code for SEN5X after branch rebase * Add placeholder for admin message * Add VOC measurements and persistence (WIP) * Adds VOC measurements and state * Still not working on VOC Index persistence * Should it stay in continuous mode? * Add one-shot mode config flag to SEN5X * Add nan checks on sensor data from SEN5X * Working implementation on VOCState * Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h) * Adds conditions for stability and sensor state * Fixes on VOC state and mode swtiching * Adds a new RHT/Gas only mode, with 3600s stabilization time * Fixes the VOCState buffer mismatch * Fixes SEN50/54/55 model mistake * Adapt SEN5X to new sensor list structure. Improve reclock. * Improve reClockI2C conditions for different variants * Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery * Add SEN5X * Fix merge errors * Small reordering in PMS class for consistency * If one sensor fails, AQ telemetry still reports data * Small formatting fix * Add SEN5X to AQI in ScanI2C * SCD4X now part of AQ module with template list * Fixes difference between idle and sleep * In LowPower, sleep is disabled * Requires testing for I2C clock comms for commands * Remove unnecessary import * Add co2 to serialized AQ metrics * Update library dependencies in platformio.ini * Fix unitialized variables in SEN5X constructor * Fix missing import * Fix uninitMemberVars * Fix import error for SCD4X * Fix I2CClock logic * Fix not reclocking back to 700000Hz * Fix multiple sensors being read simultaneously * The logic in AQ module is different to the one in EnvironmentTelemetryModule. In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data. * Fix pending clock change in PMSA003 * Cleanup of SEN5X class * Exclude AQ sensor from wio-e5 due to flash limitations * Fix I2C clock change logic * Make sure clock is always set to needed value * Fix returns * Fix trunk * Fix on condition in reclock * Fix trunk * Add check on polling interval of sen5x * Add missing serializer --------- Co-authored-by: Hannes Fuchs Co-authored-by: Nashui-Yan Co-authored-by: Ben Meadors Co-authored-by: Tom Fifield Co-authored-by: Mike Robbins Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: WillyJL Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- src/detect/ScanI2C.cpp | 2 +- src/modules/Telemetry/AirQualityTelemetry.cpp | 50 +- src/modules/Telemetry/Sensor/SCD4XSensor.cpp | 893 ++++++++++++++++++ src/modules/Telemetry/Sensor/SCD4XSensor.h | 63 ++ src/serialization/MeshPacketSerializer.cpp | 21 +- .../MeshPacketSerializer_nRF52.cpp | 18 +- 6 files changed, 1016 insertions(+), 31 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/SCD4XSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SCD4XSensor.h diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 4795d2abc..bf01a0365 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const ScanI2C::FoundDevice ScanI2C::firstAQI() const { - ScanI2C::DeviceType types[] = {PMSA003I, SCD4X}; + ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X}; return firstOfOrNONE(2, types); } diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 2ccb147c3..aac398eb9 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -22,6 +22,9 @@ #include "Sensor/AddI2CSensorTemplate.h" #include "Sensor/PMSA003ISensor.h" #include "Sensor/SEN5XSensor.h" +#if __has_include() +#include "Sensor/SCD4XSensor.h" +#endif void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { @@ -44,6 +47,9 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) // order by priority of metrics/values (low top, high bottom) addSensor(i2cScanner, ScanI2C::DeviceType::PMSA003I); addSensor(i2cScanner, ScanI2C::DeviceType::SEN5X); +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SCD4X); +#endif } int32_t AirQualityTelemetryModule::runOnce() @@ -186,7 +192,7 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta const auto &m = telemetry.variant.air_quality_metrics; // Check if any telemetry field has valid data - bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard; + bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_co2; if (!hasAny) { display->drawString(x, currentY, "No Telemetry"); @@ -213,6 +219,8 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta entries.push_back("PM2.5: " + String(m.pm25_standard) + "ug/m3"); if (m.has_pm100_standard) entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3"); + if (m.has_co2) + entries.push_back("CO2: " + String(m.co2) + "ppm"); // === Show first available metric on top-right of first line === if (!entries.empty()) { @@ -256,6 +264,9 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack // LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i", // t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental, // t->variant.air_quality_metrics.pm100_environmental); + + LOG_INFO(" | CO2=%i, CO2_T=%f, CO2_H=%f", t->variant.air_quality_metrics.co2, + t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity); #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -269,15 +280,20 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) { - bool valid = true; + // Note: this is different to the case in EnvironmentTelemetryModule + // There, if any sensor fails to read - valid = false. + bool valid = false; bool hasSensor = false; m->time = getTime(); m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag; m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero; + bool sensor_get = false; for (TelemetrySensor *sensor : sensors) { LOG_DEBUG("Reading %s", sensor->sensorName); - valid = valid && sensor->getMetrics(m); + // Note - this function doesn't get properly called if within a conditional + sensor_get = sensor->getMetrics(m); + valid = valid || sensor_get; hasSensor = true; } @@ -319,12 +335,28 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.time = getTime(); if (getAirQualityTelemetry(&m)) { - LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u", m.variant.air_quality_metrics.pm10_standard, - m.variant.air_quality_metrics.pm25_standard, m.variant.air_quality_metrics.pm100_standard); - if (m.variant.air_quality_metrics.has_pm10_environmental) - LOG_INFO("pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u", - m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental, - m.variant.air_quality_metrics.pm100_environmental); + + bool hasAnyPM = + m.variant.air_quality_metrics.has_pm10_standard || m.variant.air_quality_metrics.has_pm25_standard || + m.variant.air_quality_metrics.has_pm100_standard || m.variant.air_quality_metrics.has_pm10_environmental || + m.variant.air_quality_metrics.has_pm25_environmental || m.variant.air_quality_metrics.has_pm100_environmental; + + if (hasAnyPM) { + LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u", m.variant.air_quality_metrics.pm10_standard, + m.variant.air_quality_metrics.pm25_standard, m.variant.air_quality_metrics.pm100_standard); + if (m.variant.air_quality_metrics.has_pm10_environmental) + LOG_INFO("pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u", + m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental, + m.variant.air_quality_metrics.pm100_environmental); + } + + bool hasAnyCO2 = m.variant.air_quality_metrics.has_co2 || m.variant.air_quality_metrics.has_co2_temperature || + m.variant.air_quality_metrics.has_co2_humidity; + + if (hasAnyCO2) { + LOG_INFO("Send: co2=%i, co2_t=%f, co2_rh=%f", m.variant.air_quality_metrics.co2, + m.variant.air_quality_metrics.co2_temperature, m.variant.air_quality_metrics.co2_humidity); + } meshtastic_MeshPacket *p = allocDataProtobuf(m); p->to = dest; diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp new file mode 100644 index 000000000..4f6e28b4b --- /dev/null +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp @@ -0,0 +1,893 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../detect/reClockI2C.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SCD4XSensor.h" + +#define SCD4X_NO_ERROR 0 + +SCD4XSensor::SCD4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SCD4X, "SCD4X") {} + +bool SCD4XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + LOG_INFO("Init sensor: %s", sensorName); + + _bus = bus; + _address = dev->address.address; + +#ifdef SCD4X_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD4X_I2C_CLOCK_SPEED */ + + scd4x.begin(*_bus, _address); + + // From SCD4X library + delay(30); + + // Stop periodic measurement + if (!stopMeasurement()) { +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + // Get sensor variant + scd4x.getSensorVariant(sensorVariant); + + if (sensorVariant == SCD4X_SENSOR_VARIANT_SCD41) { + LOG_INFO("%s: Found SCD41", sensorName); + if (!powerUp()) { + LOG_ERROR("%s: Error trying to execute powerUp()", sensorName); +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + } + + if (!getASC(ascActive)) { + LOG_ERROR("%s: Unable to check if ASC is enabled", sensorName); +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + // Start measurement in selected power mode (low power by default) + if (!startMeasurement()) { + LOG_ERROR("%s: Couldn't start measurement", sensorName); +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + if (state == SCD4X_MEASUREMENT) { + status = 1; + } else { + status = 0; + } + + initI2CSensor(); + + return true; +} + +bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + + if (state != SCD4X_MEASUREMENT) { + LOG_ERROR("%s: Not in measurement mode", sensorName); + return false; + } + + uint16_t co2, error; + float temperature, humidity; + +#ifdef SCD4X_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD4X_I2C_CLOCK_SPEED */ + + bool dataReady; + error = scd4x.getDataReadyStatus(dataReady); + if (!dataReady) { +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + LOG_ERROR("SCD4X: Data is not ready"); + return false; + } + + error = scd4x.readMeasurement(co2, temperature, humidity); + +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + LOG_DEBUG("%s readings: %u ppm, %.2f degC, %.2f %rh", sensorName, co2, temperature, humidity); + if (error != SCD4X_NO_ERROR) { + LOG_DEBUG("%s: Error while getting measurements: %u", sensorName, error); + if (co2 == 0) { + LOG_ERROR("%s: Skipping invalid measurement.", sensorName); + } + return false; + } else { + measurement->variant.air_quality_metrics.has_co2_temperature = true; + measurement->variant.air_quality_metrics.has_co2_humidity = true; + measurement->variant.air_quality_metrics.has_co2 = true; + measurement->variant.air_quality_metrics.co2_temperature = temperature; + measurement->variant.air_quality_metrics.co2_humidity = humidity; + measurement->variant.air_quality_metrics.co2 = co2; + return true; + } +} + +/** + * @brief Perform a forced recalibration (FRC) of the CO₂ concentration. + * + * From Sensirion SCD4X I2C Library + * + * 1. Operate the SCD4x in the operation mode later used for normal sensor + * operation (e.g. periodic measurement) for at least 3 minutes in an + * environment with a homogenous and constant CO2 concentration. The sensor + * must be operated at the voltage desired for the application when + * performing the FRC sequence. 2. Issue the stop_periodic_measurement + * command. 3. Issue the perform_forced_recalibration command. + * @note This function should not change the clock + */ +bool SCD4XSensor::performFRC(uint32_t targetCO2) +{ + uint16_t error, frcCorr; + + LOG_INFO("%s: Issuing FRC. Ensure device has been working at least 3 minutes in stable target environment", sensorName); + + if (!stopMeasurement()) { + return false; + } + + LOG_INFO("%s: Target CO2: %u ppm", sensorName, targetCO2); + error = scd4x.performForcedRecalibration((uint16_t)targetCO2, frcCorr); + + // SCD4X Sensirion datasheet + delay(400); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to perform forced recalibration.", sensorName); + return false; + } + + if (frcCorr == 0xFFFF) { + LOG_ERROR("%s: Error while performing forced recalibration.", sensorName); + return false; + } + + LOG_INFO("%s: FRC Correction successful. Correction output: %u", sensorName, (uint16_t)(frcCorr - 0x8000)); + + return true; +} + +/** + * @brief Start measurement mode + * @note This function should not change the clock + */ +bool SCD4XSensor::startMeasurement() +{ + uint16_t error; + + if (state == SCD4X_MEASUREMENT) { + LOG_DEBUG("%s: Already in measurement mode", sensorName); + return true; + } + + if (lowPower) { + error = scd4x.startLowPowerPeriodicMeasurement(); + } else { + error = scd4x.startPeriodicMeasurement(); + } + + if (error == SCD4X_NO_ERROR) { + LOG_INFO("%s: Started measurement mode", sensorName); + if (lowPower) { + LOG_INFO("%s: Low power mode", sensorName); + } else { + LOG_INFO("%s: Normal power mode", sensorName); + } + + state = SCD4X_MEASUREMENT; + return true; + } else { + LOG_ERROR("%s: Couldn't start measurement mode", sensorName); + return false; + } +} + +/** + * @brief Stop measurement mode + * @note This function should not change the clock + */ +bool SCD4XSensor::stopMeasurement() +{ + uint16_t error; + + error = scd4x.stopPeriodicMeasurement(); + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to set idle mode on SCD4X.", sensorName); + return false; + } + + state = SCD4X_IDLE; + co2MeasureStarted = 0; + return true; +} + +/** + * @brief Set power mode + * Pass true to set low power mode + * @note This function should not change the clock + */ +bool SCD4XSensor::setPowerMode(bool _lowPower) +{ + lowPower = _lowPower; + + if (!stopMeasurement()) { + return false; + } + + if (lowPower) { + LOG_DEBUG("%s: Set low power mode", sensorName); + } else { + LOG_DEBUG("%s: Set normal power mode", sensorName); + } + + return true; +} + +/** + * @brief Check the current mode (ASC or FRC) + * From Sensirion SCD4X I2C Library + * @note This function should not change the clock + */ +bool SCD4XSensor::getASC(uint16_t &_ascActive) +{ + uint16_t error; + LOG_INFO("%s: Getting ASC", sensorName); + + if (!stopMeasurement()) { + return false; + } + error = scd4x.getAutomaticSelfCalibrationEnabled(_ascActive); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to send command.", sensorName); + return false; + } + + if (_ascActive) { + LOG_INFO("%s: ASC is enabled", sensorName); + } else { + LOG_INFO("%s: FRC is enabled", sensorName); + } + + return true; +} + +/** + * @brief Enable or disable automatic self calibration (ASC). + * + * From Sensirion SCD4X I2C Library + * + * Sets the current state (enabled / disabled) of the ASC. By default, ASC + * is enabled. + * @note This function should not change the clock + */ +bool SCD4XSensor::setASC(bool ascEnabled) +{ + uint16_t error; + + if (ascEnabled) { + LOG_INFO("%s: Enabling ASC", sensorName); + } else { + LOG_INFO("%s: Disabling ASC", sensorName); + } + + if (!stopMeasurement()) { + return false; + } + + error = scd4x.setAutomaticSelfCalibrationEnabled((uint16_t)ascEnabled); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to send command.", sensorName); + return false; + } + + error = scd4x.persistSettings(); + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to make settings persistent.", sensorName); + return false; + } + + if (!getASC(ascActive)) { + LOG_ERROR("%s: Unable to check if ASC is enabled", sensorName); + return false; + } + + if (ascActive) { + LOG_INFO("%s: ASC is enabled", sensorName); + } else { + LOG_INFO("%s: ASC is disabled", sensorName); + } + + return true; +} + +/** + * @brief Set the value of ASC baseline target in ppm. + * + * From Sensirion SCD4X I2C Library. + * + * Sets the value of the ASC baseline target, i.e. the CO₂ concentration in + * ppm which the ASC algorithm will assume as lower-bound background to + * which the SCD4x is exposed to regularly within one ASC period of + * operation. To save the setting to the EEPROM, the persist_settings + * command must be issued subsequently. The factory default value is 400 + * ppm. + * @note This function should not change the clock + */ +bool SCD4XSensor::setASCBaseline(uint32_t targetCO2) +{ + // TODO - Remove? + // Available in library, but not described in datasheet. + uint16_t error; + LOG_INFO("%s: Setting ASC baseline to: %u", sensorName, targetCO2); + + getASC(ascActive); + if (!ascActive) { + LOG_ERROR("%s: Can't set ASC baseline. ASC is not active", sensorName); + return false; + } + + if (!stopMeasurement()) { + return false; + } + + error = scd4x.setAutomaticSelfCalibrationTarget((uint16_t)targetCO2); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to send command.", sensorName); + return false; + } + + error = scd4x.persistSettings(); + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to make settings persistent.", sensorName); + return false; + } + + LOG_INFO("%s: Setting ASC baseline successful", sensorName); + + return true; +} + +/** + * @brief Set the temperature compensation reference. + * + * From Sensirion SCD4X I2C Library. + * + * Setting the temperature offset of the SCD4x inside the customer device + * allows the user to optimize the RH and T output signal. + * By default, the temperature offset is set to 4 °C. To save + * the setting to the EEPROM, the persist_settings command may be issued. + * Equation (1) details how the characteristic temperature offset can be + * calculated using the current temperature output of the sensor (TSCD4x), a + * reference temperature value (TReference), and the previous temperature + * offset (Toffset_pervious) obtained using the get_temperature_offset_raw + * command: + * + * Toffset_actual = TSCD4x - TReference + Toffset_pervious. + * + * Recommended temperature offset values are between 0 °C and 20 °C. The + * temperature offset does not impact the accuracy of the CO2 output. + * @note This function should not change the clock + */ +bool SCD4XSensor::setTemperature(float tempReference) +{ + uint16_t error; + float prevTempOffset; + float updatedTempOffset; + float tempOffset; + bool dataReady; + uint16_t co2; + float temperature; + float humidity; + + LOG_INFO("%s: Setting reference temperature at: %.2f", sensorName, tempReference); + + error = scd4x.getDataReadyStatus(dataReady); + if (!dataReady) { + LOG_ERROR("%s: Data is not ready", sensorName); + return false; + } + + error = scd4x.readMeasurement(co2, temperature, humidity); + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to read current temperature. Error code: %u", sensorName, error); + return false; + } + + LOG_INFO("%s: Current sensor temperature: %.2f", sensorName, temperature); + + if (!stopMeasurement()) { + return false; + } + + error = scd4x.getTemperatureOffset(prevTempOffset); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to get temperature offset. Error code: %u", sensorName, error); + return false; + } + LOG_INFO("%s: Current sensor temperature offset: %.2f", sensorName, prevTempOffset); + + tempOffset = temperature - tempReference + prevTempOffset; + + LOG_INFO("%s: Setting temperature offset: %.2f", sensorName, tempOffset); + error = scd4x.setTemperatureOffset(tempOffset); + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to set temperature offset. Error code: %u", sensorName, error); + return false; + } + + error = scd4x.persistSettings(); + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error); + return false; + } + + scd4x.getTemperatureOffset(updatedTempOffset); + LOG_INFO("%s: Updated sensor temperature offset: %.2f", sensorName, updatedTempOffset); + + return true; +} + +/** + * @brief Get the sensor altitude. + * + * From Sensirion SCD4X I2C Library. + * + * Altitude in meters above sea level can be set after device installation. + * Valid value between 0 and 3000m. This overrides pressure offset. + * @note This function should not change the clock + */ +bool SCD4XSensor::getAltitude(uint16_t &altitude) +{ + uint16_t error; + LOG_INFO("%s: Requesting sensor altitude", sensorName); + + if (!stopMeasurement()) { + return false; + } + + error = scd4x.getSensorAltitude(altitude); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to get altitude. Error code: %u", sensorName, error); + return false; + } + LOG_INFO("%s: Sensor altitude: %u", sensorName, altitude); + + return true; +} + +/** + * @brief Get the ambient pressure around the sensor. + * + * From Sensirion SCD4X I2C Library. + * + * Gets the ambient pressure in Pa. + * @note This function should not change the clock + */ +bool SCD4XSensor::getAmbientPressure(uint32_t &ambientPressure) +{ + uint16_t error; + LOG_INFO("%s: Requesting sensor ambient pressure", sensorName); + + error = scd4x.getAmbientPressure(ambientPressure); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to get altitude. Error code: %u", sensorName, error); + return false; + } + LOG_INFO("%s: Sensor ambient pressure: %u", sensorName, ambientPressure); + + return true; +} + +/** + * @brief Set the sensor altitude. + * + * From Sensirion SCD4X I2C Library. + * + * Altitude in meters above sea level can be set after device installation. + * Valid value between 0 and 3000m. This overrides pressure offset. + * @note This function should not change the clock + */ +bool SCD4XSensor::setAltitude(uint32_t altitude) +{ + uint16_t error; + + if (!stopMeasurement()) { + return false; + } + + error = scd4x.setSensorAltitude(altitude); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to set altitude. Error code: %u", sensorName, error); + return false; + } + + error = scd4x.persistSettings(); + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error); + return false; + } + + return true; +} + +/** + * @brief Set the ambient pressure around the sensor. + * + * From Sensirion SCD4X I2C Library. + * + * The set_ambient_pressure command can be sent during periodic measurements + * to enable continuous pressure compensation. Note that setting an ambient + * pressure overrides any pressure compensation based on a previously set + * sensor altitude. Use of this command is highly recommended for + * applications experiencing significant ambient pressure changes to ensure + * sensor accuracy. Valid input values are between 70000 - 120000 Pa. The + * default value is 101300 Pa. + * @note This function should not change the clock + */ +bool SCD4XSensor::setAmbientPressure(uint32_t ambientPressure) +{ + uint16_t error; + + error = scd4x.setAmbientPressure(ambientPressure); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to set altitude. Error code: %u", sensorName, error); + return false; + } + + // Sensirion doesn't indicate if this is necessary. We send it anyway + error = scd4x.persistSettings(); + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error); + return false; + } + + return true; +} + +/** + * @brief Perform factory reset to erase the settings stored in the EEPROM. + * + * From Sensirion SCD4X I2C Library. + * + * The perform_factory_reset command resets all configuration settings + * stored in the EEPROM and erases the FRC and ASC algorithm history. + * @note This function should not change the clock + */ +bool SCD4XSensor::factoryReset() +{ + uint16_t error; + + LOG_INFO("%s: Requesting factory reset", sensorName); + + if (!stopMeasurement()) { + return false; + } + + error = scd4x.performFactoryReset(); + + if (error != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Unable to do factory reset. Error code: %u", sensorName, error); + return false; + } + + LOG_INFO("%s: Factory reset successful", sensorName); + + return true; +} + +/** + * @brief Put the sensor into sleep mode from idle mode. + * + * From Sensirion SCD4X I2C Library. + * + * Put the sensor from idle to sleep to reduce power consumption. Can be + * used to power down when operating the sensor in power-cycled single shot + * mode. + * @note This command is only available in idle mode. Only for SCD41. + */ +bool SCD4XSensor::powerDown() +{ + LOG_INFO("%s: Trying to send sensor to sleep", sensorName); + + if (sensorVariant != SCD4X_SENSOR_VARIANT_SCD41) { + LOG_WARN("SCD4X: Can't send sensor to sleep. Incorrect variant. Ignoring"); + return true; + } + +#ifdef SCD4X_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD4X_I2C_CLOCK_SPEED */ + + if (!stopMeasurement()) { +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + if (scd4x.powerDown() != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Error trying to execute sleep()", sensorName); +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + state = SCD4X_OFF; + return true; +} + +/** + * @brief Wake up sensor from sleep mode to idle mode (powerUp) + * + * From Sensirion SCD4X I2C Library. + * + * Wake up the sensor from sleep mode into idle mode. Note that the SCD4x + * does not acknowledge the wake_up command. The sensor's idle state after + * wake up can be verified by reading out the serial number. + * @note This command is only available for SCD41. + * @note This function can't change clock (used in init) + */ +bool SCD4XSensor::powerUp() +{ + LOG_INFO("%s: Waking up", sensorName); + + if (scd4x.wakeUp() != SCD4X_NO_ERROR) { + LOG_ERROR("%s: Error trying to execute wakeUp()", sensorName); + return false; + } + + state = SCD4X_IDLE; + + return true; +} + +/** + * @brief Check if sensor is in measurement mode + */ +bool SCD4XSensor::isActive() +{ + return state == SCD4X_MEASUREMENT; +} + +/** + * @brief Start measurement mode + * @note Not used in admin comands, getMetrics or init, can change clock. + */ +uint32_t SCD4XSensor::wakeUp() +{ + +#ifdef SCD4X_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return 0; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD4X_I2C_CLOCK_SPEED */ + + if (startMeasurement()) { + co2MeasureStarted = getTime(); +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return SCD4X_WARMUP_MS; + } + +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + return 0; +} + +/** + * @brief Stop measurement mode + * @note Not used in admin comands, getMetrics or init, can change clock. + */ +void SCD4XSensor::sleep() +{ +#ifdef SCD4X_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD4X_I2C_CLOCK_SPEED */ + + stopMeasurement(); + +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif +} + +/** + * @brief Can sleep function + * + * Power consumption is very low on lowPower mode, modify this function if + * you still want to override this behaviour. Otherwise, sleep is disabled + * routinely in low power mode + */ +bool SCD4XSensor::canSleep() +{ + return lowPower ? false : true; +} + +int32_t SCD4XSensor::wakeUpTimeMs() +{ + return SCD4X_WARMUP_MS; +} + +int32_t SCD4XSensor::pendingForReadyMs() +{ + uint32_t now; + now = getTime(); + uint32_t sinceCO2MeasureStarted = (now - co2MeasureStarted) * 1000; + LOG_DEBUG("%s: Since measure started: %ums", sensorName, sinceCO2MeasureStarted); + + if (sinceCO2MeasureStarted < SCD4X_WARMUP_MS) { + LOG_INFO("%s: not enough time passed since starting measurement", sensorName); + return SCD4X_WARMUP_MS - sinceCO2MeasureStarted; + } + return 0; +} + +AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) +{ + AdminMessageHandleResult result; + +#ifdef SCD4X_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return AdminMessageHandleResult::NOT_HANDLED; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD4X_I2C_CLOCK_SPEED */ + + // TODO: potentially add selftest command? + switch (request->which_payload_variant) { + case meshtastic_AdminMessage_sensor_config_tag: + // Check for ASC-FRC request first + if (!request->sensor_config.has_scd4x_config) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } + + if (request->sensor_config.scd4x_config.has_factory_reset) { + LOG_DEBUG("%s: Requested factory reset", sensorName); + this->factoryReset(); + } else { + + if (request->sensor_config.scd4x_config.has_set_asc) { + this->setASC(request->sensor_config.scd4x_config.set_asc); + if (request->sensor_config.scd4x_config.set_asc == false) { + LOG_DEBUG("%s: Request for FRC", sensorName); + if (request->sensor_config.scd4x_config.has_set_target_co2_conc) { + this->performFRC(request->sensor_config.scd4x_config.set_target_co2_conc); + } else { + // FRC requested but no target CO2 provided + LOG_ERROR("%s: target CO2 not provided", sensorName); + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } + } else { + LOG_DEBUG("%s: Request for ASC", sensorName); + if (request->sensor_config.scd4x_config.has_set_target_co2_conc) { + LOG_DEBUG("%s: Request has target CO2", sensorName); + // TODO - Remove? see setASCBaseline function + this->setASCBaseline(request->sensor_config.scd4x_config.set_target_co2_conc); + } else { + LOG_DEBUG("%s: Request doesn't have target CO2", sensorName); + } + } + } + + // Check for temperature offset + // NOTE: this requires to have a sensor working on stable environment + // And to make it between readings + if (request->sensor_config.scd4x_config.has_set_temperature) { + this->setTemperature(request->sensor_config.scd4x_config.set_temperature); + } + + // Check for altitude or pressure offset + if (request->sensor_config.scd4x_config.has_set_altitude) { + this->setAltitude(request->sensor_config.scd4x_config.set_altitude); + } else if (request->sensor_config.scd4x_config.has_set_ambient_pressure) { + this->setAmbientPressure(request->sensor_config.scd4x_config.set_ambient_pressure); + } + + // Check for low power mode + // NOTE: to switch from one mode to another do: + // setPowerMode -> startMeasurement + if (request->sensor_config.scd4x_config.has_set_power_mode) { + this->setPowerMode(request->sensor_config.scd4x_config.set_power_mode); + } + } + + // Start measurement mode + this->startMeasurement(); + + result = AdminMessageHandleResult::HANDLED; + break; + + default: + result = AdminMessageHandleResult::NOT_HANDLED; + } + +#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + return result; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.h b/src/modules/Telemetry/Sensor/SCD4XSensor.h new file mode 100644 index 000000000..1ed86a183 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.h @@ -0,0 +1,63 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "RTC.h" +#include "TelemetrySensor.h" +#include + +// Max speed 400kHz +#define SCD4X_I2C_CLOCK_SPEED 100000 +#define SCD4X_WARMUP_MS 5000 + +class SCD4XSensor : public TelemetrySensor +{ + private: + SensirionI2cScd4x scd4x; + TwoWire *_bus{}; + uint8_t _address{}; + + bool performFRC(uint32_t targetCO2); + bool setASCBaseline(uint32_t targetCO2); + bool getASC(uint16_t &ascEnabled); + bool setASC(bool ascEnabled); + bool setTemperature(float tempReference); + bool getAltitude(uint16_t &altitude); + bool setAltitude(uint32_t altitude); + bool getAmbientPressure(uint32_t &ambientPressure); + bool setAmbientPressure(uint32_t ambientPressure); + bool factoryReset(); + bool setPowerMode(bool _lowPower); + bool startMeasurement(); + bool stopMeasurement(); + + uint16_t ascActive = 1; + // low power measurement mode (on sensirion side). Disables sleep mode + // Improvement and testing needed for timings + bool lowPower = true; + uint32_t co2MeasureStarted = 0; + + public: + SCD4XSensor(); + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + + enum SCD4XState { SCD4X_OFF, SCD4X_IDLE, SCD4X_MEASUREMENT }; + SCD4XState state = SCD4X_OFF; + SCD4xSensorVariant sensorVariant{}; + + virtual bool isActive() override; + + virtual void sleep() override; // Stops measurement (measurement -> idle) + virtual uint32_t wakeUp() override; // Starts measurement (idle -> measurement) + bool powerDown(); // Powers down sensor (idle -> power-off) + bool powerUp(); // Powers the sensor (power-off -> idle) + virtual bool canSleep() override; + virtual int32_t wakeUpTimeMs() override; + virtual int32_t pendingForReadyMs() override; + AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) override; +}; + +#endif \ No newline at end of file diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index 92e700368..042bc3763 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -149,18 +149,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.air_quality_metrics.has_pm100_standard) { msgPayload["pm100"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_standard); } - // if (decoded->variant.air_quality_metrics.has_pm10_environmental) { - // msgPayload["pm10_e"] = - // new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_environmental); - // } - // if (decoded->variant.air_quality_metrics.has_pm25_environmental) { - // msgPayload["pm25_e"] = - // new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_environmental); - // } - // if (decoded->variant.air_quality_metrics.has_pm100_environmental) { - // msgPayload["pm100_e"] = - // new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_environmental); - // } + if (decoded->variant.air_quality_metrics.has_co2) { + msgPayload["co2"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.co2); + } + if (decoded->variant.air_quality_metrics.has_co2_temperature) { + msgPayload["co2_temperature"] = new JSONValue(decoded->variant.air_quality_metrics.co2_temperature); + } + if (decoded->variant.air_quality_metrics.has_co2_humidity) { + msgPayload["co2_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.co2_humidity); + } } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { if (decoded->variant.power_metrics.has_ch1_voltage) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index 80aef0e94..a0ad4e4b9 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -120,15 +120,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.air_quality_metrics.has_pm100_standard) { jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard; } - // if (decoded->variant.air_quality_metrics.has_pm10_environmental) { - // jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental; - // } - // if (decoded->variant.air_quality_metrics.has_pm25_environmental) { - // jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental; - // } - // if (decoded->variant.air_quality_metrics.has_pm100_environmental) { - // jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental; - // } + if (decoded->variant.air_quality_metrics.has_co2) { + jsonObj["payload"]["co2"] = (unsigned int)decoded->variant.air_quality_metrics.co2; + } + if (decoded->variant.air_quality_metrics.has_co2_temperature) { + jsonObj["payload"]["co2_temperature"] = decoded->variant.air_quality_metrics.co2_temperature; + } + if (decoded->variant.air_quality_metrics.has_co2_humidity) { + jsonObj["payload"]["co2_humidity"] = decoded->variant.air_quality_metrics.co2_humidity; + } } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { if (decoded->variant.power_metrics.has_ch1_voltage) { jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; From 28c4acf5c4b5780e7b1dda990abb563b4995006a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 08:54:35 -0600 Subject: [PATCH 109/223] chore(deps): update gxepd2 to v1.6.7 (#9577) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/m5stack_coreink/platformio.ini | 2 +- variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini | 2 +- variants/esp32s3/esp32-s3-pico/platformio.ini | 2 +- variants/esp32s3/t-deck-pro/platformio.ini | 2 +- variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini | 2 +- variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini | 2 +- variants/nrf52840/MakePython_nRF52840_eink/platformio.ini | 2 +- variants/nrf52840/TWC_mesh_v4/platformio.ini | 2 +- variants/nrf52840/rak4631_epaper/platformio.ini | 2 +- variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/variants/esp32/m5stack_coreink/platformio.ini b/variants/esp32/m5stack_coreink/platformio.ini index af1535f59..7393c291f 100644 --- a/variants/esp32/m5stack_coreink/platformio.ini +++ b/variants/esp32/m5stack_coreink/platformio.ini @@ -19,7 +19,7 @@ build_flags = lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 lib_ignore = diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index e5fae20d5..69f3a4af8 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -11,7 +11,7 @@ upload_speed = 921600 lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.3 build_unflags = diff --git a/variants/esp32s3/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini index f75ab4f24..f24c84aa7 100644 --- a/variants/esp32s3/esp32-s3-pico/platformio.ini +++ b/variants/esp32s3/esp32-s3-pico/platformio.ini @@ -23,6 +23,6 @@ build_flags = ${esp32s3_base.build_flags} lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.3 diff --git a/variants/esp32s3/t-deck-pro/platformio.ini b/variants/esp32s3/t-deck-pro/platformio.ini index 5ba82d045..29960f280 100644 --- a/variants/esp32s3/t-deck-pro/platformio.ini +++ b/variants/esp32s3/t-deck-pro/platformio.ini @@ -33,7 +33,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 # renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip # renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328 diff --git a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini index 093c3732d..e202715b0 100644 --- a/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini +++ b/variants/nrf52840/Dongle_nRF52840-pca10059-v1/platformio.ini @@ -12,5 +12,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 debug_tool = jlink diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini index 5951a37a3..afc8e4b2a 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/platformio.ini @@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS0 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) upload_protocol = nrfutil ;upload_port = /dev/ttyACM1 diff --git a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini index ecb630bb7..3410859b1 100644 --- a/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini +++ b/variants/nrf52840/MakePython_nRF52840_eink/platformio.ini @@ -15,6 +15,6 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 debug_tool = jlink ;upload_port = /dev/ttyACM4 \ No newline at end of file diff --git a/variants/nrf52840/TWC_mesh_v4/platformio.ini b/variants/nrf52840/TWC_mesh_v4/platformio.ini index d93f179c2..5e5b4b665 100644 --- a/variants/nrf52840/TWC_mesh_v4/platformio.ini +++ b/variants/nrf52840/TWC_mesh_v4/platformio.ini @@ -9,5 +9,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mes lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 debug_tool = jlink diff --git a/variants/nrf52840/rak4631_epaper/platformio.ini b/variants/nrf52840/rak4631_epaper/platformio.ini index f0da832cb..ba48cf2a2 100644 --- a/variants/nrf52840/rak4631_epaper/platformio.ini +++ b/variants/nrf52840/rak4631_epaper/platformio.ini @@ -15,7 +15,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 # renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028 melopero/Melopero RV3028@1.2.0 # renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library diff --git a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini index 112ddfc29..714737c9d 100644 --- a/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini +++ b/variants/nrf52840/rak4631_epaper_onrxtx/platformio.ini @@ -17,7 +17,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631 lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 - zinggjm/GxEPD2@1.6.6 + zinggjm/GxEPD2@1.6.7 # renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028 melopero/Melopero RV3028@1.2.0 # renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library From 0df8719fc83ab583db80758865b2147fa218dba8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 9 Feb 2026 13:30:27 -0600 Subject: [PATCH 110/223] Temporarily un-renovate libch341 (#9587) --- variants/native/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index b86420291..eaf6a0e56 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -28,7 +28,7 @@ lib_deps = rweather/Crypto@0.4.0 # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 - # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main + ; # 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 adafruit/Adafruit seesaw Library@1.7.9 From 139e45dff88a5bb726e065a34f55e8d479d231f0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:52:01 -0600 Subject: [PATCH 111/223] Update protobufs (#9588) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/telemetry.pb.h | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/protobufs b/protobufs index e80cb2e41..27591d98c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit e80cb2e410d8054f5da67ba6767613c4336b6c88 +Subproject commit 27591d98c4ca58630ddcc8bd4d3055033873a56c diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index dc9d876dc..acdc9db0e 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -103,7 +103,13 @@ typedef enum _meshtastic_TelemetrySensorType { /* TSL2561 light sensor */ meshtastic_TelemetrySensorType_TSL2561 = 44, /* BH1750 light sensor */ - meshtastic_TelemetrySensorType_BH1750 = 45 + meshtastic_TelemetrySensorType_BH1750 = 45, + /* HDC1080 Temperature and Humidity Sensor */ + meshtastic_TelemetrySensorType_HDC1080 = 46, + /* STH21 Temperature and R. Humidity sensor */ + meshtastic_TelemetrySensorType_SHT21 = 47, + /* Sensirion STC31 CO2 sensor */ + meshtastic_TelemetrySensorType_STC31 = 48 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -461,8 +467,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BH1750 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BH1750+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_STC31 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_STC31+1)) From df0ee74b303ca558d48086badd388b1aa448d1b1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 9 Feb 2026 17:33:06 -0600 Subject: [PATCH 112/223] Add battery curve for T-Beam 1 watt (#9585) --- variants/esp32s3/t-beam-1w/variant.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/esp32s3/t-beam-1w/variant.h b/variants/esp32s3/t-beam-1w/variant.h index dbe1620e2..5b2e868e3 100644 --- a/variants/esp32s3/t-beam-1w/variant.h +++ b/variants/esp32s3/t-beam-1w/variant.h @@ -76,6 +76,8 @@ #define BATTERY_SENSE_SAMPLES 30 #define ADC_MULTIPLIER 2.9333 +#define OCV_ARRAY 7950, 7850, 7750, 7580, 7440, 7310, 7150, 7005, 6860, 6685, 6000 + // NTC temperature sensor #define NTC_PIN 14 From 927a4e47b8ebfa6a0dd2eb490c7020ec874ed06d Mon Sep 17 00:00:00 2001 From: Jason P Date: Mon, 9 Feb 2026 18:56:22 -0600 Subject: [PATCH 113/223] Update built-in documentation for current method of implementation (#9592) --- src/modules/ReplyBotModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/ReplyBotModule.cpp b/src/modules/ReplyBotModule.cpp index 544b3a1d9..d391bf093 100644 --- a/src/modules/ReplyBotModule.cpp +++ b/src/modules/ReplyBotModule.cpp @@ -9,8 +9,8 @@ * received the bot responds with a short status message that includes the hop count * (minimum number of relays), RSSI and SNR of the received packet. To avoid spamming * the network it enforces a per‑sender cooldown between responses. By default the - * module is enabled; define MESHTASTIC_EXCLUDE_REPLYBOT at build time to exclude it - * entirely. See the official firmware documentation for guidance on adding modules. + * module is disabled. See the official firmware documentation for guidance on adding modules. + * To enable this module, set `#undef MESHTASTIC_EXCLUDE_REPLYBOT` in your variant.h file. */ #include "Channels.h" From a092f6bb22f6fae223acf50e3306a9063f790fbd Mon Sep 17 00:00:00 2001 From: Tom <116762865+NomDeTom@users.noreply.github.com> Date: Tue, 10 Feb 2026 01:56:48 +0000 Subject: [PATCH 114/223] Refactor logging in ProtobufModule to ensure message details are logged after successful decoding (#9536) --- src/mesh/ProtobufModule.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index 725477eae..27e653efe 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -82,7 +82,6 @@ template class ProtobufModule : protected SinglePortModule // it would be better to update even if the message was destined to others. auto &p = mp.decoded; - LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum, p.payload.size); T scratch; T *decoded = NULL; @@ -90,6 +89,8 @@ template class ProtobufModule : protected SinglePortModule memset(&scratch, 0, sizeof(scratch)); if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) { decoded = &scratch; + LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum, + p.payload.size); } else { LOG_ERROR("Error decoding proto module!"); // if we can't decode it, nobody can process it! From 6df044940861cb7fe3f75285469796840087d7a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:30:50 -0600 Subject: [PATCH 115/223] Upgrade trunk (#9581) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 36154affa..c8b3e9720 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,9 +9,9 @@ plugins: lint: enabled: - checkov@3.2.500 - - renovate@43.4.0 + - renovate@43.5.4 - prettier@3.8.1 - - trufflehog@3.93.0 + - trufflehog@3.93.1 - yamllint@1.38.0 - bandit@1.9.3 - trivy@0.69.1 From 65adfa894f1b74013630a25776584c4a2773b2b0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:21:34 -0600 Subject: [PATCH 116/223] Automated version bumps (#9604) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 6 ++++++ version.properties | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 6ad8962d1..3566ad506 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.20 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19 diff --git a/debian/changelog b/debian/changelog index 38489b074..1e688ce80 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.20.0) unstable; urgency=medium + + * Version 2.7.20 + + -- GitHub Actions Wed, 11 Feb 2026 12:19:54 +0000 + meshtasticd (2.7.19.0) unstable; urgency=medium * Version 2.7.19 diff --git a/version.properties b/version.properties index 62145da14..1bbb87be7 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 19 +build = 20 From 44941b79cd2123e1f6187d939ad28c46bbacf20b Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Wed, 11 Feb 2026 12:22:01 +0000 Subject: [PATCH 117/223] Add missing openocd_target to custom nrf52 boards (#9603) This stops platformio complaining about `Missing target configuration for me25ls01-4y10td` etc when trying to flash with nrfutil. --- boards/me25ls01-4y10td.json | 3 ++- boards/meshlink.json | 3 ++- boards/minimesh_lite.json | 3 ++- boards/ms24sf1.json | 3 ++- boards/nano-g2-ultra.json | 3 ++- boards/promicro-nrf52840.json | 3 ++- boards/tracker-t1000-e.json | 3 ++- boards/wio-sdk-wm1110.json | 3 ++- boards/wio-t1000-s.json | 3 ++- boards/wio-tracker-wm1110.json | 3 ++- 10 files changed, 20 insertions(+), 10 deletions(-) diff --git a/boards/me25ls01-4y10td.json b/boards/me25ls01-4y10td.json index 9e1d63265..e0be46d67 100644 --- a/boards/me25ls01-4y10td.json +++ b/boards/me25ls01-4y10td.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Minesemi ME25LS01", diff --git a/boards/meshlink.json b/boards/meshlink.json index a608de88a..51190502e 100644 --- a/boards/meshlink.json +++ b/boards/meshlink.json @@ -33,7 +33,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "MeshLink", diff --git a/boards/minimesh_lite.json b/boards/minimesh_lite.json index 0b8f0b909..c94985531 100644 --- a/boards/minimesh_lite.json +++ b/boards/minimesh_lite.json @@ -31,7 +31,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Minimesh Lite", diff --git a/boards/ms24sf1.json b/boards/ms24sf1.json index 8356e3012..3f65a39c6 100644 --- a/boards/ms24sf1.json +++ b/boards/ms24sf1.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "MINEWSEMI_MS24SF1_SX1262", diff --git a/boards/nano-g2-ultra.json b/boards/nano-g2-ultra.json index 7afce178b..1de7c0186 100644 --- a/boards/nano-g2-ultra.json +++ b/boards/nano-g2-ultra.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "BQ nRF52840", diff --git a/boards/promicro-nrf52840.json b/boards/promicro-nrf52840.json index 99ae3f01e..e7539e0cf 100644 --- a/boards/promicro-nrf52840.json +++ b/boards/promicro-nrf52840.json @@ -33,7 +33,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "ProMicro compatible nRF52840", diff --git a/boards/tracker-t1000-e.json b/boards/tracker-t1000-e.json index 9e8870041..15bae896f 100644 --- a/boards/tracker-t1000-e.json +++ b/boards/tracker-t1000-e.json @@ -33,7 +33,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Seeed T1000-E", diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json index f45b030d1..b0dc5326a 100644 --- a/boards/wio-sdk-wm1110.json +++ b/boards/wio-sdk-wm1110.json @@ -25,7 +25,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino", "freertos"], "name": "Seeed WIO WM1110", diff --git a/boards/wio-t1000-s.json b/boards/wio-t1000-s.json index 654a8f73d..3b61f3683 100644 --- a/boards/wio-t1000-s.json +++ b/boards/wio-t1000-s.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Seeed WIO WM1110", diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json index 37a9186ab..3137f68e7 100644 --- a/boards/wio-tracker-wm1110.json +++ b/boards/wio-tracker-wm1110.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Seeed WIO WM1110", From 6b73ac3850a4a2708f9a80d3ea97747c3bd4f581 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:24:46 -0600 Subject: [PATCH 118/223] Upgrade trunk (#9599) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index c8b3e9720..65c0c9107 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,9 +9,9 @@ plugins: lint: enabled: - checkov@3.2.500 - - renovate@43.5.4 + - renovate@43.8.1 - prettier@3.8.1 - - trufflehog@3.93.1 + - trufflehog@3.93.2 - yamllint@1.38.0 - bandit@1.9.3 - trivy@0.69.1 From 75f3d123f3009032c04ff5bd4f9912453e47d75a Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 11 Feb 2026 06:29:16 -0600 Subject: [PATCH 119/223] Add sdl libs for native builds (#9595) * Add sdl libs for native builds * Alpine try again --- .clusterfuzzlite/Dockerfile | 2 +- Dockerfile | 4 ++-- alpine.Dockerfile | 4 ++-- debian/control | 3 ++- meshtasticd.spec.rpkg | 1 + 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile index 54b5cda0f..6114417c9 100644 --- a/.clusterfuzzlite/Dockerfile +++ b/.clusterfuzzlite/Dockerfile @@ -20,7 +20,7 @@ ENV PIP_ROOT_USER_ACTION=ignore RUN apt-get update && apt-get install --no-install-recommends -y \ cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \ libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \ - libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev && \ + libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev libsdl2-dev && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ pip install --no-cache-dir -U \ platformio==6.1.16 \ diff --git a/Dockerfile b/Dockerfile index 91d3f7796..e00d81658 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ curl wget g++ zip git ca-certificates pkg-config \ libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \ libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \ - libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev \ + libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir -U platformio \ && mkdir /tmp/firmware @@ -53,7 +53,7 @@ USER root RUN apt-get update && apt-get --no-install-recommends -y install \ libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \ liborcania2.3 libulfius2.7t64 libssl3t64 \ - libx11-6 libinput10 libxkbcommon-x11-0 \ + libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && mkdir -p /var/lib/meshtasticd \ && mkdir -p /etc/meshtasticd/config.d \ diff --git a/alpine.Dockerfile b/alpine.Dockerfile index 64c281788..75c9aa594 100644 --- a/alpine.Dockerfile +++ b/alpine.Dockerfile @@ -11,7 +11,7 @@ RUN apk --no-cache add \ bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \ libgpiod-dev yaml-cpp-dev bluez-dev \ libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \ - libx11-dev libinput-dev libxkbcommon-dev sqlite-dev \ + libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \ && rm -rf /var/cache/apk/* \ && pip install --no-cache-dir -U platformio \ && mkdir /tmp/firmware @@ -42,7 +42,7 @@ USER root RUN apk --no-cache add \ shadow libstdc++ libbsd libgpiod yaml-cpp libusb \ - i2c-tools libuv libx11 libinput libxkbcommon \ + i2c-tools libuv libx11 libinput libxkbcommon sdl2 \ && rm -rf /var/cache/apk/* \ && mkdir -p /var/lib/meshtasticd \ && mkdir -p /etc/meshtasticd/config.d \ diff --git a/debian/control b/debian/control index 46c932a80..8e5f17af9 100644 --- a/debian/control +++ b/debian/control @@ -26,7 +26,8 @@ Build-Depends: debhelper-compat (= 13), libx11-dev, libinput-dev, libxkbcommon-x11-dev, - libsqlite3-dev + libsqlite3-dev, + libsdl2-dev Standards-Version: 4.6.2 Homepage: https://github.com/meshtastic/firmware Rules-Requires-Root: no diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index ceb954b21..a9eb552d7 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -49,6 +49,7 @@ BuildRequires: pkgconfig(libulfius) BuildRequires: pkgconfig(x11) BuildRequires: pkgconfig(libinput) BuildRequires: pkgconfig(xkbcommon-x11) +BuildRequires: pkgconfig(sdl2) # libbsd is needed on older Fedora/RHEL to provide 'strlcpy' %if 0%{?fedora} >= 39 || 0%{?rhel} >= 10 From e047397642b2124db71023aff19c26913b6681bd Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Wed, 11 Feb 2026 12:22:01 +0000 Subject: [PATCH 120/223] Add missing openocd_target to custom nrf52 boards (#9603) This stops platformio complaining about `Missing target configuration for me25ls01-4y10td` etc when trying to flash with nrfutil. --- boards/me25ls01-4y10td.json | 3 ++- boards/meshlink.json | 3 ++- boards/minimesh_lite.json | 3 ++- boards/ms24sf1.json | 3 ++- boards/nano-g2-ultra.json | 3 ++- boards/promicro-nrf52840.json | 3 ++- boards/tracker-t1000-e.json | 3 ++- boards/wio-sdk-wm1110.json | 3 ++- boards/wio-t1000-s.json | 3 ++- boards/wio-tracker-wm1110.json | 3 ++- 10 files changed, 20 insertions(+), 10 deletions(-) diff --git a/boards/me25ls01-4y10td.json b/boards/me25ls01-4y10td.json index 9e1d63265..e0be46d67 100644 --- a/boards/me25ls01-4y10td.json +++ b/boards/me25ls01-4y10td.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Minesemi ME25LS01", diff --git a/boards/meshlink.json b/boards/meshlink.json index a608de88a..51190502e 100644 --- a/boards/meshlink.json +++ b/boards/meshlink.json @@ -33,7 +33,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "MeshLink", diff --git a/boards/minimesh_lite.json b/boards/minimesh_lite.json index 0b8f0b909..c94985531 100644 --- a/boards/minimesh_lite.json +++ b/boards/minimesh_lite.json @@ -31,7 +31,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Minimesh Lite", diff --git a/boards/ms24sf1.json b/boards/ms24sf1.json index 8356e3012..3f65a39c6 100644 --- a/boards/ms24sf1.json +++ b/boards/ms24sf1.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "MINEWSEMI_MS24SF1_SX1262", diff --git a/boards/nano-g2-ultra.json b/boards/nano-g2-ultra.json index 7afce178b..1de7c0186 100644 --- a/boards/nano-g2-ultra.json +++ b/boards/nano-g2-ultra.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "BQ nRF52840", diff --git a/boards/promicro-nrf52840.json b/boards/promicro-nrf52840.json index 99ae3f01e..e7539e0cf 100644 --- a/boards/promicro-nrf52840.json +++ b/boards/promicro-nrf52840.json @@ -33,7 +33,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "ProMicro compatible nRF52840", diff --git a/boards/tracker-t1000-e.json b/boards/tracker-t1000-e.json index 9e8870041..15bae896f 100644 --- a/boards/tracker-t1000-e.json +++ b/boards/tracker-t1000-e.json @@ -33,7 +33,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Seeed T1000-E", diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json index f45b030d1..b0dc5326a 100644 --- a/boards/wio-sdk-wm1110.json +++ b/boards/wio-sdk-wm1110.json @@ -25,7 +25,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino", "freertos"], "name": "Seeed WIO WM1110", diff --git a/boards/wio-t1000-s.json b/boards/wio-t1000-s.json index 654a8f73d..3b61f3683 100644 --- a/boards/wio-t1000-s.json +++ b/boards/wio-t1000-s.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Seeed WIO WM1110", diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json index 37a9186ab..3137f68e7 100644 --- a/boards/wio-tracker-wm1110.json +++ b/boards/wio-tracker-wm1110.json @@ -32,7 +32,8 @@ "connectivity": ["bluetooth"], "debug": { "jlink_device": "nRF52840_xxAA", - "svd_path": "nrf52840.svd" + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" }, "frameworks": ["arduino"], "name": "Seeed WIO WM1110", From 4954723f977cb7bf52fd518c90f0f0c1ea1cf580 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:21:34 -0600 Subject: [PATCH 121/223] Automated version bumps (#9604) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 6 ++++++ version.properties | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 6ad8962d1..3566ad506 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.20 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19 diff --git a/debian/changelog b/debian/changelog index 38489b074..1e688ce80 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.20.0) unstable; urgency=medium + + * Version 2.7.20 + + -- GitHub Actions Wed, 11 Feb 2026 12:19:54 +0000 + meshtasticd (2.7.19.0) unstable; urgency=medium * Version 2.7.19 diff --git a/version.properties b/version.properties index 62145da14..1bbb87be7 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 19 +build = 20 From f96a8593f253b94d93aabdccae9441250d2a9565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 11 Feb 2026 13:43:05 +0100 Subject: [PATCH 122/223] fix some random compiler warnings (#9596) --- src/main.cpp | 7 ++++++- src/mesh/RadioInterface.cpp | 4 ++-- src/modules/Telemetry/AirQualityTelemetry.cpp | 6 +++--- src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 44885813e..54b0bc3e6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -349,9 +349,10 @@ void setup() #endif concurrency::hasBeenSetup = true; - +#if HAS_SCREEN meshtastic_Config_DisplayConfig_OledType screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; +#endif OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64; #ifdef USE_SEGGER @@ -568,6 +569,7 @@ void setup() } #endif +#if HAS_SCREEN auto screenInfo = i2cScanner->firstScreen(); screen_found = screenInfo.type != ScanI2C::DeviceType::NONE ? screenInfo.address : ScanI2C::ADDRESS_NONE; @@ -585,6 +587,7 @@ void setup() screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; } } +#endif #define UPDATE_FROM_SCANNER(FIND_FN) #if defined(USE_VIRTUAL_KEYBOARD) @@ -723,6 +726,7 @@ void setup() else playStartMelody(); +#if HAS_SCREEN // fixed screen override? if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) screen_model = config.display.oled; @@ -735,6 +739,7 @@ void setup() #if defined(USE_SH1107_128_64) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64 #endif +#endif #if !MESHTASTIC_EXCLUDE_I2C #if !defined(ARCH_STM32WL) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index bbd766329..9e1ea3f21 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -943,9 +943,9 @@ void RadioInterface::limitPower(int8_t loraMaxPower) } } else if (!devicestate.owner.is_licensed) { // we have an array of PA gain values. Find the highest power setting that works. - for (int radio_dbm = 0; radio_dbm < num_pa_points; radio_dbm++) { + for (int radio_dbm = 0; radio_dbm < (int)num_pa_points; radio_dbm++) { if (((radio_dbm + tx_gain[radio_dbm]) > power) || - ((radio_dbm == (num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { + ((radio_dbm == (int)(num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { // we've exceeded the power limit, or hit the max we can do LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]); power -= tx_gain[radio_dbm]; diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index aac398eb9..cc1b54373 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -137,9 +137,9 @@ int32_t AirQualityTelemetryModule::runOnce() LOG_DEBUG("Sending sensors to sleep"); for (TelemetrySensor *sensor : sensors) { if (sensor->isActive() && sensor->canSleep()) { - if (sensor->wakeUpTimeMs() < Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs, - numOnlineNodes)) { + if (sensor->wakeUpTimeMs() < + (int32_t)Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes)) { LOG_DEBUG("Disabling %s until next period", sensor->sensorName); sensor->sleep(); } else { diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp index fc20ce1f2..2d890ca99 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -572,7 +572,7 @@ bool SEN5XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) // Check if it is time to do a cleaning uint32_t now; - int32_t passed; + int32_t passed = 0; now = getValidTime(RTCQuality::RTCQualityDevice); // If time is not RTCQualityNone, it will return non-zero From 5b03b1fe5294df024bdd8890f82ed82ebd0ef044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 11 Feb 2026 13:43:05 +0100 Subject: [PATCH 123/223] fix some random compiler warnings (#9596) --- src/main.cpp | 7 ++++++- src/mesh/RadioInterface.cpp | 4 ++-- src/modules/Telemetry/AirQualityTelemetry.cpp | 6 +++--- src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 415d23f44..52359a6a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -374,9 +374,10 @@ void setup() #endif concurrency::hasBeenSetup = true; - +#if HAS_SCREEN meshtastic_Config_DisplayConfig_OledType screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; +#endif OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64; #ifdef USE_SEGGER @@ -601,6 +602,7 @@ void setup() } #endif +#if HAS_SCREEN auto screenInfo = i2cScanner->firstScreen(); screen_found = screenInfo.type != ScanI2C::DeviceType::NONE ? screenInfo.address : ScanI2C::ADDRESS_NONE; @@ -618,6 +620,7 @@ void setup() screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO; } } +#endif #define UPDATE_FROM_SCANNER(FIND_FN) #if defined(USE_VIRTUAL_KEYBOARD) @@ -763,6 +766,7 @@ void setup() else playStartMelody(); +#if HAS_SCREEN // fixed screen override? if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) screen_model = config.display.oled; @@ -775,6 +779,7 @@ void setup() #if defined(USE_SH1107_128_64) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64 #endif +#endif #if !MESHTASTIC_EXCLUDE_I2C #if !defined(ARCH_STM32WL) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index bbd766329..9e1ea3f21 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -943,9 +943,9 @@ void RadioInterface::limitPower(int8_t loraMaxPower) } } else if (!devicestate.owner.is_licensed) { // we have an array of PA gain values. Find the highest power setting that works. - for (int radio_dbm = 0; radio_dbm < num_pa_points; radio_dbm++) { + for (int radio_dbm = 0; radio_dbm < (int)num_pa_points; radio_dbm++) { if (((radio_dbm + tx_gain[radio_dbm]) > power) || - ((radio_dbm == (num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { + ((radio_dbm == (int)(num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { // we've exceeded the power limit, or hit the max we can do LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]); power -= tx_gain[radio_dbm]; diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index aac398eb9..cc1b54373 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -137,9 +137,9 @@ int32_t AirQualityTelemetryModule::runOnce() LOG_DEBUG("Sending sensors to sleep"); for (TelemetrySensor *sensor : sensors) { if (sensor->isActive() && sensor->canSleep()) { - if (sensor->wakeUpTimeMs() < Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs, - numOnlineNodes)) { + if (sensor->wakeUpTimeMs() < + (int32_t)Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes)) { LOG_DEBUG("Disabling %s until next period", sensor->sensorName); sensor->sleep(); } else { diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp index fc20ce1f2..2d890ca99 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -572,7 +572,7 @@ bool SEN5XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) // Check if it is time to do a cleaning uint32_t now; - int32_t passed; + int32_t passed = 0; now = getValidTime(RTCQuality::RTCQualityDevice); // If time is not RTCQualityNone, it will return non-zero From 648148af8a7e6ee7fe9a3c19b30c9cc7cdcd968b Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:50:04 +0800 Subject: [PATCH 124/223] Modify the dependency library of v4-tft (#9507) --- variants/esp32s3/heltec_v4/platformio.ini | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 8b951b5e9..72c53ded0 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -124,11 +124,8 @@ build_flags = -D SCREEN_TOUCH_RST=TOUCH_RST_PIN lib_deps = ${heltec_v4_base.lib_deps} - ; ${device-ui_base.lib_deps} + ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 # 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 - ; TODO revert to official device-ui (when merged) - # renovate: datasource=git-refs depName=Quency-D_device-ui packageName=https://github.com/Quency-D/device-ui gitBranch=heltec-v4-tft - https://github.com/Quency-D/device-ui/archive/7c9870b8016641190b059bdd90fe16c1012a39eb.zip + https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip \ No newline at end of file From 21d5a34feca7ad93a9ad2691b65044117d755478 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 07:05:35 -0600 Subject: [PATCH 125/223] Update protobufs (#9605) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 8 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 14 +++- .../generated/meshtastic/module_config.pb.cpp | 3 + .../generated/meshtastic/module_config.pb.h | 78 ++++++++++++++++++- .../generated/meshtastic/telemetry.pb.cpp | 3 + src/mesh/generated/meshtastic/telemetry.pb.h | 49 +++++++++++- 8 files changed, 148 insertions(+), 11 deletions(-) diff --git a/protobufs b/protobufs index 27591d98c..e1a6b3a86 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 27591d98c4ca58630ddcc8bd4d3055033873a56c +Subproject commit e1a6b3a868d735da72cd6c94c574d655129d390a diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index f545ed9bf..336510ec3 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -79,7 +79,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12, /* TODO: REPLACE */ - meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13 + meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13, + /* Traffic management module config */ + meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -369,8 +371,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 2aaa12da5..5bbd87ffd 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2364 +#define meshtastic_BackupPreferences_size 2419 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index b2b71733c..7a5874b64 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -90,6 +90,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* StatusMessage Config */ bool has_statusmessage; meshtastic_ModuleConfig_StatusMessageConfig statusmessage; + /* The part of the config that is specific to the Traffic Management module */ + bool has_traffic_management; + meshtastic_ModuleConfig_TrafficManagementConfig traffic_management; } meshtastic_LocalModuleConfig; @@ -99,9 +102,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -128,6 +131,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_detection_sensor_tag 13 #define meshtastic_LocalModuleConfig_paxcounter_tag 14 #define meshtastic_LocalModuleConfig_statusmessage_tag 15 +#define meshtastic_LocalModuleConfig_traffic_management_tag 16 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -166,7 +170,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ -X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) +X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) \ +X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -183,6 +188,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) #define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig #define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig +#define meshtastic_LocalModuleConfig_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -194,7 +200,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 751 -#define meshtastic_LocalModuleConfig_size 758 +#define meshtastic_LocalModuleConfig_size 813 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index bb57c3f2d..b9705b0bc 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -30,6 +30,9 @@ PB_BIND(meshtastic_ModuleConfig_AudioConfig, meshtastic_ModuleConfig_AudioConfig PB_BIND(meshtastic_ModuleConfig_PaxcounterConfig, meshtastic_ModuleConfig_PaxcounterConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_TrafficManagementConfig, meshtastic_ModuleConfig_TrafficManagementConfig, AUTO) + + PB_BIND(meshtastic_ModuleConfig_SerialConfig, meshtastic_ModuleConfig_SerialConfig, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 46a7164d2..67dbe06e7 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -228,6 +228,39 @@ typedef struct _meshtastic_ModuleConfig_PaxcounterConfig { int32_t ble_threshold; } meshtastic_ModuleConfig_PaxcounterConfig; +/* Config for the Traffic Management module. + Provides packet inspection and traffic shaping to help reduce channel utilization */ +typedef struct _meshtastic_ModuleConfig_TrafficManagementConfig { + /* Master enable for traffic management module */ + bool enabled; + /* Enable position deduplication to drop redundant position broadcasts */ + bool position_dedup_enabled; + /* Number of bits of precision for position deduplication (0-32) */ + uint32_t position_precision_bits; + /* Minimum interval in seconds between position updates from the same node */ + uint32_t position_min_interval_secs; + /* Enable direct response to NodeInfo requests from local cache */ + bool nodeinfo_direct_response; + /* Minimum hop distance from requestor before responding to NodeInfo requests */ + uint32_t nodeinfo_direct_response_max_hops; + /* Enable per-node rate limiting to throttle chatty nodes */ + bool rate_limit_enabled; + /* Time window in seconds for rate limiting calculations */ + uint32_t rate_limit_window_secs; + /* Maximum packets allowed per node within the rate limit window */ + uint32_t rate_limit_max_packets; + /* Enable dropping of unknown/undecryptable packets per rate_limit_window_secs */ + bool drop_unknown_enabled; + /* Number of unknown packets before dropping from a node */ + uint32_t unknown_packet_threshold; + /* Set hop_limit to 0 for relayed telemetry broadcasts (own packets unaffected) */ + bool exhaust_hop_telemetry; + /* Set hop_limit to 0 for relayed position broadcasts (own packets unaffected) */ + bool exhaust_hop_position; + /* Preserve hop_limit for router-to-router traffic */ + bool router_preserve_hops; +} meshtastic_ModuleConfig_TrafficManagementConfig; + /* Serial Config */ typedef struct _meshtastic_ModuleConfig_SerialConfig { /* Preferences for the SerialModule */ @@ -468,6 +501,8 @@ typedef struct _meshtastic_ModuleConfig { meshtastic_ModuleConfig_PaxcounterConfig paxcounter; /* TODO: REPLACE */ meshtastic_ModuleConfig_StatusMessageConfig statusmessage; + /* Traffic management module config for mesh network optimization */ + meshtastic_ModuleConfig_TrafficManagementConfig traffic_management; } payload_variant; } meshtastic_ModuleConfig; @@ -511,6 +546,7 @@ extern "C" { #define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud + #define meshtastic_ModuleConfig_SerialConfig_baud_ENUMTYPE meshtastic_ModuleConfig_SerialConfig_Serial_Baud #define meshtastic_ModuleConfig_SerialConfig_mode_ENUMTYPE meshtastic_ModuleConfig_SerialConfig_Serial_Mode @@ -536,6 +572,7 @@ extern "C" { #define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0} #define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0} +#define meshtastic_ModuleConfig_TrafficManagementConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} @@ -553,6 +590,7 @@ extern "C" { #define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0} #define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0} +#define meshtastic_ModuleConfig_TrafficManagementConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} @@ -600,6 +638,20 @@ extern "C" { #define meshtastic_ModuleConfig_PaxcounterConfig_paxcounter_update_interval_tag 2 #define meshtastic_ModuleConfig_PaxcounterConfig_wifi_threshold_tag 3 #define meshtastic_ModuleConfig_PaxcounterConfig_ble_threshold_tag 4 +#define meshtastic_ModuleConfig_TrafficManagementConfig_enabled_tag 1 +#define meshtastic_ModuleConfig_TrafficManagementConfig_position_dedup_enabled_tag 2 +#define meshtastic_ModuleConfig_TrafficManagementConfig_position_precision_bits_tag 3 +#define meshtastic_ModuleConfig_TrafficManagementConfig_position_min_interval_secs_tag 4 +#define meshtastic_ModuleConfig_TrafficManagementConfig_nodeinfo_direct_response_tag 5 +#define meshtastic_ModuleConfig_TrafficManagementConfig_nodeinfo_direct_response_max_hops_tag 6 +#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_enabled_tag 7 +#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_window_secs_tag 8 +#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_max_packets_tag 9 +#define meshtastic_ModuleConfig_TrafficManagementConfig_drop_unknown_enabled_tag 10 +#define meshtastic_ModuleConfig_TrafficManagementConfig_unknown_packet_threshold_tag 11 +#define meshtastic_ModuleConfig_TrafficManagementConfig_exhaust_hop_telemetry_tag 12 +#define meshtastic_ModuleConfig_TrafficManagementConfig_exhaust_hop_position_tag 13 +#define meshtastic_ModuleConfig_TrafficManagementConfig_router_preserve_hops_tag 14 #define meshtastic_ModuleConfig_SerialConfig_enabled_tag 1 #define meshtastic_ModuleConfig_SerialConfig_echo_tag 2 #define meshtastic_ModuleConfig_SerialConfig_rxd_tag 3 @@ -685,6 +737,7 @@ extern "C" { #define meshtastic_ModuleConfig_detection_sensor_tag 12 #define meshtastic_ModuleConfig_paxcounter_tag 13 #define meshtastic_ModuleConfig_statusmessage_tag 14 +#define meshtastic_ModuleConfig_traffic_management_tag 15 /* Struct field encoding specification for nanopb */ #define meshtastic_ModuleConfig_FIELDLIST(X, a) \ @@ -701,7 +754,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_varian X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15) #define meshtastic_ModuleConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DEFAULT NULL #define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -718,6 +772,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_varian #define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig #define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig +#define meshtastic_ModuleConfig_payload_variant_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig #define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -788,6 +843,24 @@ X(a, STATIC, SINGULAR, INT32, ble_threshold, 4) #define meshtastic_ModuleConfig_PaxcounterConfig_CALLBACK NULL #define meshtastic_ModuleConfig_PaxcounterConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_TrafficManagementConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ +X(a, STATIC, SINGULAR, BOOL, position_dedup_enabled, 2) \ +X(a, STATIC, SINGULAR, UINT32, position_precision_bits, 3) \ +X(a, STATIC, SINGULAR, UINT32, position_min_interval_secs, 4) \ +X(a, STATIC, SINGULAR, BOOL, nodeinfo_direct_response, 5) \ +X(a, STATIC, SINGULAR, UINT32, nodeinfo_direct_response_max_hops, 6) \ +X(a, STATIC, SINGULAR, BOOL, rate_limit_enabled, 7) \ +X(a, STATIC, SINGULAR, UINT32, rate_limit_window_secs, 8) \ +X(a, STATIC, SINGULAR, UINT32, rate_limit_max_packets, 9) \ +X(a, STATIC, SINGULAR, BOOL, drop_unknown_enabled, 10) \ +X(a, STATIC, SINGULAR, UINT32, unknown_packet_threshold, 11) \ +X(a, STATIC, SINGULAR, BOOL, exhaust_hop_telemetry, 12) \ +X(a, STATIC, SINGULAR, BOOL, exhaust_hop_position, 13) \ +X(a, STATIC, SINGULAR, BOOL, router_preserve_hops, 14) +#define meshtastic_ModuleConfig_TrafficManagementConfig_CALLBACK NULL +#define meshtastic_ModuleConfig_TrafficManagementConfig_DEFAULT NULL + #define meshtastic_ModuleConfig_SerialConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, BOOL, echo, 2) \ @@ -900,6 +973,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_NeighborInfoConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_DetectionSensorConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_AudioConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_PaxcounterConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_TrafficManagementConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_SerialConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_ExternalNotificationConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_StoreForwardConfig_msg; @@ -919,6 +993,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_DetectionSensorConfig_fields &meshtastic_ModuleConfig_DetectionSensorConfig_msg #define meshtastic_ModuleConfig_AudioConfig_fields &meshtastic_ModuleConfig_AudioConfig_msg #define meshtastic_ModuleConfig_PaxcounterConfig_fields &meshtastic_ModuleConfig_PaxcounterConfig_msg +#define meshtastic_ModuleConfig_TrafficManagementConfig_fields &meshtastic_ModuleConfig_TrafficManagementConfig_msg #define meshtastic_ModuleConfig_SerialConfig_fields &meshtastic_ModuleConfig_SerialConfig_msg #define meshtastic_ModuleConfig_ExternalNotificationConfig_fields &meshtastic_ModuleConfig_ExternalNotificationConfig_msg #define meshtastic_ModuleConfig_StoreForwardConfig_fields &meshtastic_ModuleConfig_StoreForwardConfig_msg @@ -946,6 +1021,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_StatusMessageConfig_size 81 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_TelemetryConfig_size 50 +#define meshtastic_ModuleConfig_TrafficManagementConfig_size 52 #define meshtastic_ModuleConfig_size 227 #define meshtastic_RemoteHardwarePin_size 21 diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp index fff75ebc1..bc21b9dcb 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.cpp +++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp @@ -21,6 +21,9 @@ PB_BIND(meshtastic_AirQualityMetrics, meshtastic_AirQualityMetrics, AUTO) PB_BIND(meshtastic_LocalStats, meshtastic_LocalStats, AUTO) +PB_BIND(meshtastic_TrafficManagementStats, meshtastic_TrafficManagementStats, AUTO) + + PB_BIND(meshtastic_HealthMetrics, meshtastic_HealthMetrics, AUTO) diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index acdc9db0e..7c7ae457a 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -371,6 +371,24 @@ typedef struct _meshtastic_LocalStats { int32_t noise_floor; } meshtastic_LocalStats; +/* Traffic management statistics for mesh network optimization */ +typedef struct _meshtastic_TrafficManagementStats { + /* Total number of packets inspected by traffic management */ + uint32_t packets_inspected; + /* Number of position packets dropped due to deduplication */ + uint32_t position_dedup_drops; + /* Number of NodeInfo requests answered from cache */ + uint32_t nodeinfo_cache_hits; + /* Number of packets dropped due to rate limiting */ + uint32_t rate_limit_drops; + /* Number of unknown/undecryptable packets dropped */ + uint32_t unknown_packet_drops; + /* Number of packets with hop_limit exhausted for local-only broadcast */ + uint32_t hop_exhausted_packets; + /* Number of times router hop preservation was applied */ + uint32_t router_hops_preserved; +} meshtastic_TrafficManagementStats; + /* Health telemetry metrics */ typedef struct _meshtastic_HealthMetrics { /* Heart rate (beats per minute) */ @@ -430,6 +448,8 @@ typedef struct _meshtastic_Telemetry { meshtastic_HealthMetrics health_metrics; /* Linux host metrics */ meshtastic_HostMetrics host_metrics; + /* Traffic management statistics */ + meshtastic_TrafficManagementStats traffic_management_stats; } variant; } meshtastic_Telemetry; @@ -481,12 +501,14 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_TrafficManagementStats_init_default {0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} @@ -497,6 +519,7 @@ extern "C" { #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define meshtastic_TrafficManagementStats_init_zero {0, 0, 0, 0, 0, 0, 0} #define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0} #define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""} #define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}} @@ -587,6 +610,13 @@ extern "C" { #define meshtastic_LocalStats_heap_free_bytes_tag 13 #define meshtastic_LocalStats_num_tx_dropped_tag 14 #define meshtastic_LocalStats_noise_floor_tag 15 +#define meshtastic_TrafficManagementStats_packets_inspected_tag 1 +#define meshtastic_TrafficManagementStats_position_dedup_drops_tag 2 +#define meshtastic_TrafficManagementStats_nodeinfo_cache_hits_tag 3 +#define meshtastic_TrafficManagementStats_rate_limit_drops_tag 4 +#define meshtastic_TrafficManagementStats_unknown_packet_drops_tag 5 +#define meshtastic_TrafficManagementStats_hop_exhausted_packets_tag 6 +#define meshtastic_TrafficManagementStats_router_hops_preserved_tag 7 #define meshtastic_HealthMetrics_heart_bpm_tag 1 #define meshtastic_HealthMetrics_spO2_tag 2 #define meshtastic_HealthMetrics_temperature_tag 3 @@ -607,6 +637,7 @@ extern "C" { #define meshtastic_Telemetry_local_stats_tag 6 #define meshtastic_Telemetry_health_metrics_tag 7 #define meshtastic_Telemetry_host_metrics_tag 8 +#define meshtastic_Telemetry_traffic_management_stats_tag 9 #define meshtastic_Nau7802Config_zeroOffset_tag 1 #define meshtastic_Nau7802Config_calibrationFactor_tag 2 #define meshtastic_SEN5XState_last_cleaning_time_tag 1 @@ -720,6 +751,17 @@ X(a, STATIC, SINGULAR, INT32, noise_floor, 15) #define meshtastic_LocalStats_CALLBACK NULL #define meshtastic_LocalStats_DEFAULT NULL +#define meshtastic_TrafficManagementStats_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, packets_inspected, 1) \ +X(a, STATIC, SINGULAR, UINT32, position_dedup_drops, 2) \ +X(a, STATIC, SINGULAR, UINT32, nodeinfo_cache_hits, 3) \ +X(a, STATIC, SINGULAR, UINT32, rate_limit_drops, 4) \ +X(a, STATIC, SINGULAR, UINT32, unknown_packet_drops, 5) \ +X(a, STATIC, SINGULAR, UINT32, hop_exhausted_packets, 6) \ +X(a, STATIC, SINGULAR, UINT32, router_hops_preserved, 7) +#define meshtastic_TrafficManagementStats_CALLBACK NULL +#define meshtastic_TrafficManagementStats_DEFAULT NULL + #define meshtastic_HealthMetrics_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, UINT32, heart_bpm, 1) \ X(a, STATIC, OPTIONAL, UINT32, spO2, 2) \ @@ -748,7 +790,8 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,air_quality_metrics,variant.air_qual X(a, STATIC, ONEOF, MESSAGE, (variant,power_metrics,variant.power_metrics), 5) \ X(a, STATIC, ONEOF, MESSAGE, (variant,local_stats,variant.local_stats), 6) \ X(a, STATIC, ONEOF, MESSAGE, (variant,health_metrics,variant.health_metrics), 7) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,host_metrics,variant.host_metrics), 8) +X(a, STATIC, ONEOF, MESSAGE, (variant,host_metrics,variant.host_metrics), 8) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,traffic_management_stats,variant.traffic_management_stats), 9) #define meshtastic_Telemetry_CALLBACK NULL #define meshtastic_Telemetry_DEFAULT NULL #define meshtastic_Telemetry_variant_device_metrics_MSGTYPE meshtastic_DeviceMetrics @@ -758,6 +801,7 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,host_metrics,variant.host_metrics), #define meshtastic_Telemetry_variant_local_stats_MSGTYPE meshtastic_LocalStats #define meshtastic_Telemetry_variant_health_metrics_MSGTYPE meshtastic_HealthMetrics #define meshtastic_Telemetry_variant_host_metrics_MSGTYPE meshtastic_HostMetrics +#define meshtastic_Telemetry_variant_traffic_management_stats_MSGTYPE meshtastic_TrafficManagementStats #define meshtastic_Nau7802Config_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, zeroOffset, 1) \ @@ -780,6 +824,7 @@ extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg; extern const pb_msgdesc_t meshtastic_PowerMetrics_msg; extern const pb_msgdesc_t meshtastic_AirQualityMetrics_msg; extern const pb_msgdesc_t meshtastic_LocalStats_msg; +extern const pb_msgdesc_t meshtastic_TrafficManagementStats_msg; extern const pb_msgdesc_t meshtastic_HealthMetrics_msg; extern const pb_msgdesc_t meshtastic_HostMetrics_msg; extern const pb_msgdesc_t meshtastic_Telemetry_msg; @@ -792,6 +837,7 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg; #define meshtastic_PowerMetrics_fields &meshtastic_PowerMetrics_msg #define meshtastic_AirQualityMetrics_fields &meshtastic_AirQualityMetrics_msg #define meshtastic_LocalStats_fields &meshtastic_LocalStats_msg +#define meshtastic_TrafficManagementStats_fields &meshtastic_TrafficManagementStats_msg #define meshtastic_HealthMetrics_fields &meshtastic_HealthMetrics_msg #define meshtastic_HostMetrics_fields &meshtastic_HostMetrics_msg #define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg @@ -810,6 +856,7 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg; #define meshtastic_PowerMetrics_size 81 #define meshtastic_SEN5XState_size 27 #define meshtastic_Telemetry_size 272 +#define meshtastic_TrafficManagementStats_size 42 #ifdef __cplusplus } /* extern "C" */ From 0adcadb1ce3fc4a2f7a300aa4fc2c9d7f75b3fdd Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:50:04 +0800 Subject: [PATCH 126/223] Modify the dependency library of v4-tft (#9507) --- variants/esp32s3/heltec_v4/platformio.ini | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 20b3b4606..6daf9a317 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -124,11 +124,8 @@ build_flags = -D SCREEN_TOUCH_RST=TOUCH_RST_PIN lib_deps = ${heltec_v4_base.lib_deps} - ; ${device-ui_base.lib_deps} + ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 # 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 - ; TODO revert to official device-ui (when merged) - # renovate: datasource=git-refs depName=Quency-D_device-ui packageName=https://github.com/Quency-D/device-ui gitBranch=heltec-v4-tft - https://github.com/Quency-D/device-ui/archive/7c9870b8016641190b059bdd90fe16c1012a39eb.zip + https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip \ No newline at end of file From 97983d8014037707b66b5b9529e782b2d34d13c5 Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:01:26 -0500 Subject: [PATCH 127/223] BaseUI: Favorite Screen Signal Quality improvement (#9566) * Favorite Signal Quality improvement * Show Voltage if node shares it. * Trunk Fix * Change Favorite tittle to encase name with Asterisks * Add Pluggin In condition for Battery Line * Adjust getUptimeStr Prefixes * Create isAPIConnected for SharedCommon usage * Correct leftSideSpacing to account for isAPIConnected --------- Co-authored-by: Jason P --- src/graphics/SharedUIDisplay.cpp | 50 +++--- src/graphics/SharedUIDisplay.h | 14 ++ src/graphics/TimeFormatters.cpp | 12 +- src/graphics/draw/DebugRenderer.cpp | 2 +- src/graphics/draw/UIRenderer.cpp | 244 ++++++++++++++++++++++++---- src/graphics/images.h | 6 + 6 files changed, 260 insertions(+), 68 deletions(-) diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index ab21b2a03..b86f3e32c 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -427,39 +427,33 @@ const int *getTextPositions(OLEDDisplay *display) // ************************* void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y) { - bool drawConnectionState = false; - if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI || - service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET || - service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) { - drawConnectionState = true; - } + if (!isAPIConnected(service->api_state)) + return; - if (drawConnectionState) { - const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1; - display->setColor(BLACK); - display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale), - (connection_icon_height * scale) + (2 * scale)); - display->setColor(WHITE); - if (currentResolution == ScreenResolution::High) { - const int bytesPerRow = (connection_icon_width + 7) / 8; - int iconX = 0; - int iconY = SCREEN_HEIGHT - (connection_icon_height * 2); + const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1; + display->setColor(BLACK); + display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale), + (connection_icon_height * scale) + (2 * scale)); + display->setColor(WHITE); + if (currentResolution == ScreenResolution::High) { + const int bytesPerRow = (connection_icon_width + 7) / 8; + int iconX = 0; + int iconY = SCREEN_HEIGHT - (connection_icon_height * 2); - for (int yy = 0; yy < connection_icon_height; ++yy) { - const uint8_t *rowPtr = connection_icon + yy * bytesPerRow; - for (int xx = 0; xx < connection_icon_width; ++xx) { - const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3)); - const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first - if (byteVal & bitMask) { - display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale); - } + for (int yy = 0; yy < connection_icon_height; ++yy) { + const uint8_t *rowPtr = connection_icon + yy * bytesPerRow; + for (int xx = 0; xx < connection_icon_width; ++xx) { + const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3)); + const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first + if (byteVal & bitMask) { + display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale); } } - - } else { - display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height, - connection_icon); } + + } else { + display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height, + connection_icon); } } diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h index a8ecdfada..35e767056 100644 --- a/src/graphics/SharedUIDisplay.h +++ b/src/graphics/SharedUIDisplay.h @@ -63,4 +63,18 @@ bool isAllowedPunctuation(char c); std::string sanitizeString(const std::string &input); +static inline bool isAPIConnected(uint8_t state) +{ + static constexpr bool connectedStates[] = { + /* STATE_NONE */ false, + /* STATE_BLE */ true, + /* STATE_WIFI */ true, + /* STATE_SERIAL */ true, + /* STATE_PACKET */ true, + /* STATE_HTTP */ true, + /* STATE_ETH */ true, + }; + return state < sizeof(connectedStates) ? connectedStates[state] : false; +} + } // namespace graphics diff --git a/src/graphics/TimeFormatters.cpp b/src/graphics/TimeFormatters.cpp index 0a1c23341..02450efa3 100644 --- a/src/graphics/TimeFormatters.cpp +++ b/src/graphics/TimeFormatters.cpp @@ -110,14 +110,14 @@ void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, ui uint32_t secs = (uptimeMillis % 60000) / 1000; if (days) { - snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours); + snprintf(uptimeStr, maxLength, "%s%ud %uh", prefix, days, hours); } else if (hours) { - snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins); + snprintf(uptimeStr, maxLength, "%s%uh %um", prefix, hours, mins); } else if (!includeSecs) { - snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins); + snprintf(uptimeStr, maxLength, "%s%um", prefix, mins); } else if (mins) { - snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs); + snprintf(uptimeStr, maxLength, "%s%um %us", prefix, mins, secs); } else { - snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs); + snprintf(uptimeStr, maxLength, "%s%us", prefix, secs); } -} +} \ No newline at end of file diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 2dca38d66..2069c71ec 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -663,7 +663,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it char uptimeStr[32] = ""; - getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr)); + getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr)); textWidth = display->getStringWidth(uptimeStr); nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], uptimeStr); diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 7ce9d5afe..922ca1028 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -2,6 +2,7 @@ #if HAS_SCREEN #include "CompassRenderer.h" #include "GPSStatus.h" +#include "MeshService.h" #include "NodeDB.h" #include "NodeListRenderer.h" #include "UIRenderer.h" @@ -313,7 +314,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st // === Create the shortName and title string === const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node"; char titlestr[32] = {0}; - snprintf(titlestr, sizeof(titlestr), "Fav: %s", shortName); + snprintf(titlestr, sizeof(titlestr), "*%s*", shortName); // === Draw battery/time/mail header (common across screens) === graphics::drawCommonHeader(display, x, y, titlestr); @@ -342,34 +343,162 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st } // === 2. Signal and Hops (combined on one line, if available) === - // If both are present: "Sig: 97% [2hops]" - // If only one: show only that one char signalHopsStr[32] = ""; bool haveSignal = false; - int percentSignal = clamp((int)((node->snr + 10) * 5), 0, 100); + int bars = 0; - // Always use "Sig" for the label - const char *signalLabel = " Sig"; + // Helper to get SNR limit based on modem preset + auto getSnrLimit = [](meshtastic_Config_LoRaConfig_ModemPreset preset) -> float { + switch (preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST: + return -6.0f; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: + return -5.5f; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + return -4.5f; + default: + return -6.0f; + } + }; + + // Calculate signal grade using modem preset and SNR only + float snrLimit = getSnrLimit(config.lora.modem_preset); + float snr = node->snr; + + // Determine signal quality label and bars using SNR-only grading + const char *qualityLabel = nullptr; + + if (snr > snrLimit + 10) { + qualityLabel = "Good"; + bars = 4; + } else if (snr > snrLimit + 6) { + qualityLabel = "Good"; + bars = 3; + } else if (snr > snrLimit + 2) { + qualityLabel = "Good"; + bars = 2; + } else if (snr > snrLimit - 4) { + qualityLabel = "Fair"; + bars = 1; + } else { + qualityLabel = "Bad"; + bars = 1; + } + + // Add extra spacing on the left if we have an API connection to account for the common footer icons + const char *leftSideSpacing = + graphics::isAPIConnected(service->api_state) ? (currentResolution == ScreenResolution::High ? " " : " ") : " "; // --- Build the Signal/Hops line --- - // If SNR looks reasonable, show signal - if ((int)((node->snr + 10) * 5) >= 0 && node->snr > -100) { - snprintf(signalHopsStr, sizeof(signalHopsStr), "%s: %d%%", signalLabel, percentSignal); + // Only show signal if we have valid SNR + if (snr > -100 && snr != 0) { + snprintf(signalHopsStr, sizeof(signalHopsStr), "%sSig:%s", leftSideSpacing, qualityLabel); haveSignal = true; } - // If hops is valid (>0), show right after signal + if (node->hops_away > 0) { size_t len = strlen(signalHopsStr); - // Decide between "1 Hop" and "N Hops" if (haveSignal) { - snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [%d %s]", node->hops_away, - (node->hops_away == 1 ? "Hop" : "Hops")); + snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [#]"); } else { - snprintf(signalHopsStr, sizeof(signalHopsStr), "[%d %s]", node->hops_away, (node->hops_away == 1 ? "Hop" : "Hops")); + snprintf(signalHopsStr, sizeof(signalHopsStr), "[#]"); } } - if (signalHopsStr[0] && line < 5) { - display->drawString(x, getTextPositions(display)[line++], signalHopsStr); + + if (signalHopsStr[0]) { + int yPos = getTextPositions(display)[line++]; + int curX = x; + + // Split combined string into signal text and hop suffix + char sigPart[20] = ""; + const char *hopPart = nullptr; + + char *bracket = strchr(signalHopsStr, '['); + if (bracket) { + size_t n = (size_t)(bracket - signalHopsStr); + if (n >= sizeof(sigPart)) + n = sizeof(sigPart) - 1; + memcpy(sigPart, signalHopsStr, n); + sigPart[n] = '\0'; + + // Trim trailing spaces + while (strlen(sigPart) && sigPart[strlen(sigPart) - 1] == ' ') { + sigPart[strlen(sigPart) - 1] = '\0'; + } + + hopPart = bracket; // "[n Hop(s)]" + } else { + strncpy(sigPart, signalHopsStr, sizeof(sigPart) - 1); + sigPart[sizeof(sigPart) - 1] = '\0'; + } + + // Draw signal quality text + display->drawString(curX, yPos, sigPart); + curX += display->getStringWidth(sigPart) + 4; + + // Draw signal bars (skip on UltraLow, text only) + if (currentResolution != ScreenResolution::UltraLow && haveSignal && bars > 0) { + const int kMaxBars = 4; + if (bars < 1) + bars = 1; + if (bars > kMaxBars) + bars = kMaxBars; + + int barX = curX; + + const bool hi = (currentResolution == ScreenResolution::High); + int barWidth = hi ? 2 : 1; + int barGap = hi ? 2 : 1; + int maxBarHeight = FONT_HEIGHT_SMALL - 7; + if (!hi) + maxBarHeight -= 1; + int barY = yPos + (FONT_HEIGHT_SMALL - maxBarHeight) / 2; + + for (int bi = 0; bi < kMaxBars; bi++) { + int barHeight = maxBarHeight * (bi + 1) / kMaxBars; + if (barHeight < 2) + barHeight = 2; + + int bx = barX + bi * (barWidth + barGap); + int by = barY + maxBarHeight - barHeight; + + if (bi < bars) { + display->fillRect(bx, by, barWidth, barHeight); + } else { + int baseY = barY + maxBarHeight - 1; + display->drawHorizontalLine(bx, baseY, barWidth); + } + } + + curX += (kMaxBars * barWidth) + ((kMaxBars - 1) * barGap) + 2; + } + + // Draw hops AFTER the bars as: [ number + hop icon ] + if (hopPart && node->hops_away > 0) { + + // open bracket + display->drawString(curX, yPos, "["); + curX += display->getStringWidth("[") + 1; + + // hop count + char hopCount[6]; + snprintf(hopCount, sizeof(hopCount), "%d", node->hops_away); + display->drawString(curX, yPos, hopCount); + curX += display->getStringWidth(hopCount) + 2; + + // hop icon + const int iconY = yPos + (FONT_HEIGHT_SMALL - hop_height) / 2; + display->drawXbm(curX, iconY, hop_width, hop_height, hop); + curX += hop_width + 1; + + // closing bracket + display->drawString(curX, yPos, "]"); + } } // === 3. Heard (last seen, skip if node never seen) === @@ -377,8 +506,8 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st uint32_t seconds = sinceLastSeen(node); if (seconds != 0 && seconds != UINT32_MAX) { uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24; - // Format as "Heard: Xm ago", "Heard: Xh ago", or "Heard: Xd ago" - snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard: ?" : " Heard: %d%c ago"), + // Format as "Heard:Xm ago", "Heard:Xh ago", or "Heard:Xd ago" + snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard:?" : "%sHeard:%d%c ago"), leftSideSpacing, (days ? days : hours ? hours : minutes), @@ -386,16 +515,18 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st : hours ? 'h' : 'm')); } - if (seenStr[0] && line < 5) { + if (seenStr[0]) { display->drawString(x, getTextPositions(display)[line++], seenStr); } #if !defined(M5STACK_UNITC6L) // === 4. Uptime (only show if metric is present) === char uptimeStr[32] = ""; if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { - getUptimeStr(node->device_metrics.uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr)); + char upPrefix[12]; // enough for leftSideSpacing + "Up:" + snprintf(upPrefix, sizeof(upPrefix), "%sUp:", leftSideSpacing); + getUptimeStr(node->device_metrics.uptime_seconds * 1000, upPrefix, uptimeStr, sizeof(uptimeStr)); } - if (uptimeStr[0] && line < 5) { + if (uptimeStr[0]) { display->drawString(x, getTextPositions(display)[line++], uptimeStr); } @@ -422,16 +553,16 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st if (miles < 0.1) { int feet = (int)(miles * 5280); if (feet > 0 && feet < 1000) { - snprintf(distStr, sizeof(distStr), " Distance: %dft", feet); + snprintf(distStr, sizeof(distStr), "%sDistance:%dft", leftSideSpacing, feet); haveDistance = true; } else if (feet >= 1000) { - snprintf(distStr, sizeof(distStr), " Distance: ¼mi"); + snprintf(distStr, sizeof(distStr), "%sDistance:¼mi", leftSideSpacing); haveDistance = true; } } else { int roundedMiles = (int)(miles + 0.5); if (roundedMiles > 0 && roundedMiles < 1000) { - snprintf(distStr, sizeof(distStr), " Distance: %dmi", roundedMiles); + snprintf(distStr, sizeof(distStr), "%sDistance:%dmi", leftSideSpacing, roundedMiles); haveDistance = true; } } @@ -439,26 +570,74 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st if (distanceKm < 1.0) { int meters = (int)(distanceKm * 1000); if (meters > 0 && meters < 1000) { - snprintf(distStr, sizeof(distStr), " Distance: %dm", meters); + snprintf(distStr, sizeof(distStr), "%sDistance:%dm", leftSideSpacing, meters); haveDistance = true; } else if (meters >= 1000) { - snprintf(distStr, sizeof(distStr), " Distance: 1km"); + snprintf(distStr, sizeof(distStr), "%sDistance:1km", leftSideSpacing); haveDistance = true; } } else { int km = (int)(distanceKm + 0.5); if (km > 0 && km < 1000) { - snprintf(distStr, sizeof(distStr), " Distance: %dkm", km); + snprintf(distStr, sizeof(distStr), "%sDistance:%dkm", leftSideSpacing, km); haveDistance = true; } } } } - // Only display if we actually have a value! - if (haveDistance && distStr[0] && line < 5) { + if (haveDistance && distStr[0]) { display->drawString(x, getTextPositions(display)[line++], distStr); } + // === 6. Battery after Distance line, otherwise next available line === + char batLine[32] = ""; + bool haveBatLine = false; + + if (node->has_device_metrics) { + bool hasPct = node->device_metrics.has_battery_level; + bool hasVolt = node->device_metrics.has_voltage && node->device_metrics.voltage > 0.001f; + + int pct = 0; + float volt = 0.0f; + + if (hasPct) { + pct = (int)node->device_metrics.battery_level; + } + + if (hasVolt) { + volt = node->device_metrics.voltage; + } + + if (hasPct && pct > 0 && pct <= 100) { + // Normal battery percentage + if (hasVolt) { + snprintf(batLine, sizeof(batLine), "%sBat:%d%% (%.2fV)", leftSideSpacing, pct, volt); + } else { + snprintf(batLine, sizeof(batLine), "%sBat:%d%%", leftSideSpacing, pct); + } + haveBatLine = true; + } else if (hasPct && pct > 100) { + // Plugged in + if (hasVolt) { + snprintf(batLine, sizeof(batLine), "%sPlugged In (%.2fV)", leftSideSpacing, volt); + } else { + snprintf(batLine, sizeof(batLine), "%sPlugged In", leftSideSpacing); + } + haveBatLine = true; + } else if (!hasPct && hasVolt) { + // Voltage only + snprintf(batLine, sizeof(batLine), "%sBat:%.2fV", leftSideSpacing, volt); + haveBatLine = true; + } + } + + const int maxTextLines = (currentResolution == ScreenResolution::High) ? 6 : 5; + + // Only draw battery if it fits within the allowed lines + if (haveBatLine && line <= maxTextLines) { + display->drawString(x, getTextPositions(display)[line++], batLine); + } + // --- Compass Rendering: landscape (wide) screens use the original side-aligned logic --- if (SCREEN_WIDTH > SCREEN_HEIGHT) { bool showCompass = false; @@ -593,7 +772,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta } char uptimeStr[32] = ""; if (currentResolution != ScreenResolution::UltraLow) { - getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr)); + getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr)); } display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr); @@ -984,7 +1163,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU config.display.heading_bold = false; const char *displayLine = ""; // Initialize to empty string by default - meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { if (config.position.fixed_position) { @@ -1029,10 +1207,10 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU char uptimeStr[32]; #if defined(USE_EINK) // E-Ink: skip seconds, show only days/hours/mins - getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false); + getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), false); #else // Non E-Ink: include seconds where useful - getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true); + getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), true); #endif display->drawString(0, getTextPositions(display)[line++], uptimeStr); diff --git a/src/graphics/images.h b/src/graphics/images.h index ef9ffef78..66fcbc79c 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -83,6 +83,12 @@ static const unsigned char mail[] PROGMEM = { 0b11111111, 0b00 // Bottom line }; +// Hop icon (9x10) +#define hop_width 9 +#define hop_height 10 +const uint8_t hop[] PROGMEM = {0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x38, 0x00, 0x28, 0x00, + 0x38, 0x00, 0xC0, 0x01, 0x40, 0x01, 0xC0, 0x01, 0x40, 0x00}; + // 📬 Mail / Message const uint8_t icon_mail[] PROGMEM = { 0b11111111, // ████████ top border From 31fe15bb73fb174f87893cac64a7d4ce1ff2d85d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 11 Feb 2026 12:02:22 -0600 Subject: [PATCH 128/223] =?UTF-8?q?ExternalNotification=20and=20StatusLED?= =?UTF-8?q?=20now=20call=20AmbientLighting=20to=20update=E2=80=A6=20(#9554?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ExternalNotification and StatusLED now call AmbientLighting to update RGB LEDs. Add optional heartbeat * Don't overwrite RGB state if heartbeat is disabled. * Use the right define * Remove another .h and make rgb static * move rgb objects into AmbientLighting class * Straighten out AmbientLighting Thread object * Use %f for floats --- src/AmbientLightingThread.h | 125 +++++++++++---------- src/graphics/NeoPixel.h | 4 - src/graphics/RAKled.h | 5 - src/main.cpp | 3 +- src/modules/ExternalNotificationModule.cpp | 98 +--------------- src/modules/ExternalNotificationModule.h | 5 + src/modules/StatusLEDModule.cpp | 10 ++ src/modules/StatusLEDModule.h | 4 + 8 files changed, 88 insertions(+), 166 deletions(-) delete mode 100644 src/graphics/NeoPixel.h delete mode 100644 src/graphics/RAKled.h diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index 947b1e054..bd9557c21 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -1,19 +1,21 @@ +#ifndef AMBIENTLIGHTINGTHREAD_H +#define AMBIENTLIGHTINGTHREAD_H + #include "Observer.h" #include "configuration.h" +#include "detect/ScanI2C.h" +#include "sleep.h" #ifdef HAS_NCP5623 -#include -NCP5623 rgb; +#include #endif #ifdef HAS_LP5562 #include -LP5562 rgbw; #endif #ifdef HAS_NEOPIXEL -#include -Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE); +#include #endif #ifdef UNPHONE @@ -21,10 +23,24 @@ Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE); extern unPhone unphone; #endif -namespace concurrency -{ class AmbientLightingThread : public concurrency::OSThread { + friend class StatusLEDModule; // Let the LEDStatusModule trigger the ambient lighting for notifications and battery status. + friend class ExternalNotificationModule; // Let the ExternalNotificationModule trigger the ambient lighting for notifications. + + private: +#ifdef HAS_NCP5623 + NCP5623 rgb; +#endif + +#ifdef HAS_LP5562 + LP5562 rgbw; +#endif + +#ifdef HAS_NEOPIXEL + Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE); +#endif + public: explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLighting") { @@ -36,14 +52,15 @@ class AmbientLightingThread : public concurrency::OSThread moduleConfig.ambient_lighting.led_state = true; #endif #endif - // Uncomment to test module - // moduleConfig.ambient_lighting.led_state = true; - // moduleConfig.ambient_lighting.current = 10; +#if AMBIENT_LIGHTING_TEST + // define to enable test + moduleConfig.ambient_lighting.led_state = true; + moduleConfig.ambient_lighting.current = 10; // Default to a color based on our node number - // moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16; - // moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; - // moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; - + moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16; + moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; + moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; +#endif #if defined(HAS_NCP5623) || defined(HAS_LP5562) _type = type; if (_type == ScanI2C::DeviceType::NONE) { @@ -53,11 +70,6 @@ class AmbientLightingThread : public concurrency::OSThread } #endif #ifdef HAS_RGB_LED - if (!moduleConfig.ambient_lighting.led_state) { - LOG_DEBUG("AmbientLighting Disable due to moduleConfig.ambient_lighting.led_state OFF"); - disable(); - return; - } LOG_DEBUG("AmbientLighting init"); #ifdef HAS_NCP5623 if (_type == ScanI2C::NCP5623) { @@ -77,7 +89,13 @@ class AmbientLightingThread : public concurrency::OSThread pixels.clear(); // Set all pixel colors to 'off' pixels.setBrightness(moduleConfig.ambient_lighting.current); #endif - setLighting(); + if (!moduleConfig.ambient_lighting.led_state) { + LOG_DEBUG("AmbientLighting Disable due to moduleConfig.ambient_lighting.led_state OFF"); + disable(); + return; + } + setLighting(moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, + moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif #if defined(HAS_NCP5623) || defined(HAS_LP5562) } @@ -91,7 +109,8 @@ class AmbientLightingThread : public concurrency::OSThread #if defined(HAS_NCP5623) || defined(HAS_LP5562) if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::LP5562) && moduleConfig.ambient_lighting.led_state) { #endif - setLighting(); + setLighting(moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, + moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification #if defined(HAS_NCP5623) || defined(HAS_LP5562) } @@ -148,65 +167,53 @@ class AmbientLightingThread : public concurrency::OSThread return 0; } - void setLighting() + protected: + void setLighting(float current, uint8_t red, uint8_t green, uint8_t blue) { #ifdef HAS_NCP5623 - rgb.setCurrent(moduleConfig.ambient_lighting.current); - rgb.setRed(moduleConfig.ambient_lighting.red); - rgb.setGreen(moduleConfig.ambient_lighting.green); - rgb.setBlue(moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init NCP5623 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", - moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + rgb.setCurrent(current); + rgb.setRed(red); + rgb.setGreen(green); + rgb.setBlue(blue); + LOG_DEBUG("Init NCP5623 Ambient light w/ current=%f, red=%d, green=%d, blue=%d", current, red, green, blue); #endif #ifdef HAS_LP5562 - rgbw.setCurrent(moduleConfig.ambient_lighting.current); - rgbw.setRed(moduleConfig.ambient_lighting.red); - rgbw.setGreen(moduleConfig.ambient_lighting.green); - rgbw.setBlue(moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init LP5562 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current, - moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + rgbw.setCurrent(current); + rgbw.setRed(red); + rgbw.setGreen(green); + rgbw.setBlue(blue); + LOG_DEBUG("Init LP5562 Ambient light w/ current=%f, red=%d, green=%d, blue=%d", current, red, green, blue); #endif #ifdef HAS_NEOPIXEL - pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, - moduleConfig.ambient_lighting.blue), - 0, NEOPIXEL_COUNT); + pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); // RadioMaster Bandit has addressable LED at the two buttons // this allow us to set different lighting for them in variant.h file. -#ifdef RADIOMASTER_900_BANDIT #if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX) pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1); #endif #if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX) pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1); -#endif #endif pixels.show(); - // LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d", - // moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, - // moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + // LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%f, red=%d, green=%d, blue=%d", + // current, red, green, blue); #endif #ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red); - analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green); - analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + analogWrite(RGBLED_RED, 255 - red); + analogWrite(RGBLED_GREEN, 255 - green); + analogWrite(RGBLED_BLUE, 255 - blue); + LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", red, green, blue); #elif defined(RGBLED_RED) - analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red); - analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green); - analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init Ambient light RGB Common Cathode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + analogWrite(RGBLED_RED, red); + analogWrite(RGBLED_GREEN, green); + analogWrite(RGBLED_BLUE, blue); + LOG_DEBUG("Init Ambient light RGB Common Cathode w/ red=%d, green=%d, blue=%d", red, green, blue); #endif #ifdef UNPHONE - unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, - moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + unphone.rgb(red, green, blue); + LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", red, green, blue); #endif } }; - -} // namespace concurrency +#endif // AMBIENTLIGHTINGTHREAD_H \ No newline at end of file diff --git a/src/graphics/NeoPixel.h b/src/graphics/NeoPixel.h deleted file mode 100644 index dde74366e..000000000 --- a/src/graphics/NeoPixel.h +++ /dev/null @@ -1,4 +0,0 @@ -#ifdef HAS_NEOPIXEL -#include -extern Adafruit_NeoPixel pixels; -#endif \ No newline at end of file diff --git a/src/graphics/RAKled.h b/src/graphics/RAKled.h deleted file mode 100644 index 659ea9b72..000000000 --- a/src/graphics/RAKled.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifdef HAS_NCP5623 -#include -extern NCP5623 rgb; - -#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 54b0bc3e6..153b9847e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,7 +29,6 @@ #include #endif #include "detect/einkScan.h" -#include "graphics/RAKled.h" #include "graphics/Screen.h" #include "main.h" #include "mesh/generated/meshtastic/config.pb.h" @@ -247,7 +246,7 @@ const char *getDeviceName() uint32_t timeLastPowered = 0; static OSThread *powerFSMthread; -static OSThread *ambientLightingThread; +OSThread *ambientLightingThread; RadioInterface *rIf = NULL; #ifdef ARCH_PORTDUINO diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 5e6985bdf..0301b2ac3 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -24,24 +24,8 @@ #include "mesh/generated/meshtastic/rtttl.pb.h" #include -#ifdef HAS_NCP5623 -#include -#endif - -#ifdef HAS_LP5562 -#include -#endif - -#ifdef HAS_NEOPIXEL -#include -#endif - -#ifdef UNPHONE -#include "unPhone.h" -extern unPhone unphone; -#endif - #if defined(HAS_RGB_LED) +#include "AmbientLightingThread.h" uint8_t red = 0; uint8_t green = 0; uint8_t blue = 0; @@ -123,32 +107,6 @@ int32_t ExternalNotificationModule::runOnce() green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7 blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7 white = (colorState & 12) ? brightnessValues[brightnessIndex] : 0; -#ifdef HAS_NCP5623 - if (rgb_found.type == ScanI2C::NCP5623) { - rgb.setColor(red, green, blue); - } -#endif -#ifdef HAS_LP5562 - if (rgb_found.type == ScanI2C::LP5562) { - rgbw.setColor(red, green, blue, white); - } -#endif -#ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic - analogWrite(RGBLED_GREEN, 255 - green); - analogWrite(RGBLED_BLUE, 255 - blue); -#elif defined(RGBLED_RED) - analogWrite(RGBLED_RED, red); - analogWrite(RGBLED_GREEN, green); - analogWrite(RGBLED_BLUE, blue); -#endif -#ifdef HAS_NEOPIXEL - pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); - pixels.show(); -#endif -#ifdef UNPHONE - unphone.rgb(red, green, blue); -#endif if (ascending) { // fade in brightnessIndex++; if (brightnessIndex == (sizeof(brightnessValues) - 1)) { @@ -255,34 +213,9 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on) blue = 0; white = 0; } + ambientLightingThread->setLighting(moduleConfig.ambient_lighting.current, red, green, blue); #endif -#ifdef HAS_NCP5623 - if (rgb_found.type == ScanI2C::NCP5623) { - rgb.setColor(red, green, blue); - } -#endif -#ifdef HAS_LP5562 - if (rgb_found.type == ScanI2C::LP5562) { - rgbw.setColor(red, green, blue, white); - } -#endif -#ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic - analogWrite(RGBLED_GREEN, 255 - green); - analogWrite(RGBLED_BLUE, 255 - blue); -#elif defined(RGBLED_RED) - analogWrite(RGBLED_RED, red); - analogWrite(RGBLED_GREEN, green); - analogWrite(RGBLED_BLUE, blue); -#endif -#ifdef HAS_NEOPIXEL - pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); - pixels.show(); -#endif -#ifdef UNPHONE - unphone.rgb(red, green, blue); -#endif #ifdef HAS_DRV2605 if (on) { drv.go(); @@ -407,33 +340,6 @@ ExternalNotificationModule::ExternalNotificationModule() LOG_INFO("Use Pin %i in PWM mode", config.device.buzzer_gpio); } } -#ifdef HAS_NCP5623 - if (rgb_found.type == ScanI2C::NCP5623) { - rgb.begin(); - rgb.setCurrent(10); - } -#endif -#ifdef HAS_LP5562 - if (rgb_found.type == ScanI2C::LP5562) { - rgbw.begin(); - rgbw.setCurrent(20); - } -#endif -#ifdef RGBLED_RED - pinMode(RGBLED_RED, OUTPUT); // set up the RGB led pins - pinMode(RGBLED_GREEN, OUTPUT); - pinMode(RGBLED_BLUE, OUTPUT); -#endif -#ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255); // with a common anode type, logic is reversed - analogWrite(RGBLED_GREEN, 255); // so we want to initialise with lights off - analogWrite(RGBLED_BLUE, 255); -#endif -#ifdef HAS_NEOPIXEL - pixels.begin(); // Initialise the pixel(s) - pixels.clear(); // Set all pixel colors to 'off' - pixels.setBrightness(moduleConfig.ambient_lighting.current); -#endif } else { LOG_INFO("External Notification Module Disabled"); disable(); diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index f667f7be9..94b021360 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -5,6 +5,11 @@ #include "configuration.h" #include "input/InputBroker.h" +#ifdef HAS_RGB_LED +#include "AmbientLightingThread.h" +extern AmbientLightingThread *ambientLightingThread; +#endif + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 7acf750c2..457935688 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -164,6 +164,16 @@ int32_t StatusLEDModule::runOnce() digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif +#ifdef RGB_LED_POWER + if (!config.device.led_heartbeat_disabled) { + if (CHARGE_LED_state == LED_STATE_ON) { + ambientLightingThread->setLighting(10, 255, 0, 0); + } else { + ambientLightingThread->setLighting(0, 0, 0, 0); + } + } +#endif + #ifdef Battery_LED_1 digitalWrite(Battery_LED_1, chargeIndicatorLED1); #endif diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index c35f29019..972e26737 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -59,3 +59,7 @@ class StatusLEDModule : private concurrency::OSThread }; extern StatusLEDModule *statusLEDModule; +#ifdef RGB_LED_POWER +#include "AmbientLightingThread.h" +extern AmbientLightingThread *ambientLightingThread; +#endif From 6d299eac67e8549b5664402d3a2aab25b56468e1 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Wed, 11 Feb 2026 20:11:11 +0100 Subject: [PATCH 129/223] Fixes on SCD4X admin comands (#9607) * Fixes on SCD4X admin comands * Minor fix in logs for SEN5X --------- Co-authored-by: Ben Meadors --- src/modules/Telemetry/Sensor/SCD4XSensor.cpp | 89 ++++++++++++++------ src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 19 ++++- 2 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp index 4f6e28b4b..579d9873b 100644 --- a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp @@ -333,12 +333,6 @@ bool SCD4XSensor::setASC(bool ascEnabled) return false; } - if (ascActive) { - LOG_INFO("%s: ASC is enabled", sensorName); - } else { - LOG_INFO("%s: ASC is disabled", sensorName); - } - return true; } @@ -358,7 +352,7 @@ bool SCD4XSensor::setASC(bool ascEnabled) bool SCD4XSensor::setASCBaseline(uint32_t targetCO2) { // TODO - Remove? - // Available in library, but not described in datasheet. + // Available in library, but not described in datasheet. uint16_t error; LOG_INFO("%s: Setting ASC baseline to: %u", sensorName, targetCO2); @@ -540,6 +534,7 @@ bool SCD4XSensor::setAltitude(uint32_t altitude) if (!stopMeasurement()) { return false; } + LOG_INFO("%s: setting altitude at %um", sensorName, altitude); error = scd4x.setSensorAltitude(altitude); @@ -548,11 +543,15 @@ bool SCD4XSensor::setAltitude(uint32_t altitude) return false; } - error = scd4x.persistSettings(); - if (error != SCD4X_NO_ERROR) { - LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error); - return false; - } + // NOTE: this gives an error if issued. Sensirion's library + // doesn't indicate it's needed. + // error = scd4x.persistSettings(); + // if (error != SCD4X_NO_ERROR) { + // LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error); + // return false; + // } + + LOG_INFO("%s: altitude set", sensorName); return true; } @@ -575,6 +574,8 @@ bool SCD4XSensor::setAmbientPressure(uint32_t ambientPressure) { uint16_t error; + LOG_INFO("%s: setting ambient pressure at %u Pa", sensorName, ambientPressure); + error = scd4x.setAmbientPressure(ambientPressure); if (error != SCD4X_NO_ERROR) { @@ -589,6 +590,8 @@ bool SCD4XSensor::setAmbientPressure(uint32_t ambientPressure) return false; } + LOG_INFO("%s: ambient pressure set set", sensorName); + return true; } @@ -824,15 +827,28 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa if (request->sensor_config.scd4x_config.has_factory_reset) { LOG_DEBUG("%s: Requested factory reset", sensorName); - this->factoryReset(); + if (!this->factoryReset()) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } else { - if (request->sensor_config.scd4x_config.has_set_asc) { - this->setASC(request->sensor_config.scd4x_config.set_asc); + getASC(ascActive); + bool currentASC = ascActive; if (request->sensor_config.scd4x_config.set_asc == false) { LOG_DEBUG("%s: Request for FRC", sensorName); if (request->sensor_config.scd4x_config.has_set_target_co2_conc) { - this->performFRC(request->sensor_config.scd4x_config.set_target_co2_conc); + if (this->setASC(request->sensor_config.scd4x_config.set_asc)) { + if (!this->performFRC(request->sensor_config.scd4x_config.set_target_co2_conc)) { + result = AdminMessageHandleResult::NOT_HANDLED; + // Set it back to ASC if failed + setASC(currentASC); + break; + }; + } else { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } else { // FRC requested but no target CO2 provided LOG_ERROR("%s: target CO2 not provided", sensorName); @@ -841,12 +857,17 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa } } else { LOG_DEBUG("%s: Request for ASC", sensorName); - if (request->sensor_config.scd4x_config.has_set_target_co2_conc) { - LOG_DEBUG("%s: Request has target CO2", sensorName); - // TODO - Remove? see setASCBaseline function - this->setASCBaseline(request->sensor_config.scd4x_config.set_target_co2_conc); + if (this->setASC(request->sensor_config.scd4x_config.set_asc)) { + if (request->sensor_config.scd4x_config.has_set_target_co2_conc) { + LOG_DEBUG("%s: Request has target CO2", sensorName); + this->setASCBaseline(request->sensor_config.scd4x_config.set_target_co2_conc); + // NOTE - in this situation, if we set ASC, but baseline set fails, we stay on ASC + } else { + LOG_DEBUG("%s: Request doesn't have target CO2", sensorName); + } } else { - LOG_DEBUG("%s: Request doesn't have target CO2", sensorName); + result = AdminMessageHandleResult::NOT_HANDLED; + break; } } } @@ -855,27 +876,36 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa // NOTE: this requires to have a sensor working on stable environment // And to make it between readings if (request->sensor_config.scd4x_config.has_set_temperature) { - this->setTemperature(request->sensor_config.scd4x_config.set_temperature); + if (!this->setTemperature(request->sensor_config.scd4x_config.set_temperature)) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } // Check for altitude or pressure offset if (request->sensor_config.scd4x_config.has_set_altitude) { - this->setAltitude(request->sensor_config.scd4x_config.set_altitude); + if (!this->setAltitude(request->sensor_config.scd4x_config.set_altitude)) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } else if (request->sensor_config.scd4x_config.has_set_ambient_pressure) { - this->setAmbientPressure(request->sensor_config.scd4x_config.set_ambient_pressure); + if (!this->setAmbientPressure(request->sensor_config.scd4x_config.set_ambient_pressure)) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } // Check for low power mode // NOTE: to switch from one mode to another do: // setPowerMode -> startMeasurement if (request->sensor_config.scd4x_config.has_set_power_mode) { - this->setPowerMode(request->sensor_config.scd4x_config.set_power_mode); + if (!this->setPowerMode(request->sensor_config.scd4x_config.set_power_mode)) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } } - // Start measurement mode - this->startMeasurement(); - result = AdminMessageHandleResult::HANDLED; break; @@ -883,6 +913,9 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa result = AdminMessageHandleResult::NOT_HANDLED; } + // Start measurement mode + this->startMeasurement(); + #if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) reClockI2C(currentClock, _bus, false); #endif diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp index 2d890ca99..299a1f7df 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -919,6 +919,11 @@ bool SEN5XSensor::getMetrics(meshtastic_Telemetry *measurement) void SEN5XSensor::setMode(bool setOneShot) { oneShotMode = setOneShot; + if (oneShotMode) { + LOG_INFO("%s setting mode to one shot mode", sensorName); + } else { + LOG_INFO("%s setting mode to continuous mode", sensorName); + } } AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, @@ -934,16 +939,22 @@ AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPa break; } - // TODO - Add admin command to set temperature offset + // Check for one-shot/continuous mode request + if (request->sensor_config.sen5x_config.has_set_one_shot_mode) { + this->setMode(request->sensor_config.sen5x_config.set_one_shot_mode); + } + + // TODO - Add admin command to set temperature offset? // Check for temperature offset // if (request->sensor_config.sen5x_config.has_set_temperature) { // this->setTemperature(request->sensor_config.sen5x_config.set_temperature); // } + // TODO - Add admin command to trigger fan cleaning? // Check for one-shot/continuous mode request - if (request->sensor_config.sen5x_config.has_set_one_shot_mode) { - this->setMode(request->sensor_config.sen5x_config.set_one_shot_mode); - } + // if (request->sensor_config.sen5x_config.has_fan_cleaning && request->sensor_config.sen5x_config.fan_cleaning) { + // this->startCleaning(); + // } result = AdminMessageHandleResult::HANDLED; break; From 15297cb56fc7820d0e9f5e25702ffb66d62fbe1b Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Wed, 11 Feb 2026 21:13:34 +0100 Subject: [PATCH 130/223] feat/add sfa30 (#9372) * Move PMSA003I to separate class and update AQ telemetry * AirQualityTelemetry module not depend on PM sensor presence * Remove commented line * Fixes on PMS class * Add missing warmup period to wakeUp function * Fixes on compilation for different variants * Add functions to check for I2C bus speed and set it * Initial implementation for SFA30Sensor * Move PMSA003I to separate class and update AQ telemetry * AirQualityTelemetry module not depend on PM sensor presence * Remove commented line * Fixes on PMS class * Add missing warmup period to wakeUp function * Fixes on compilation for different variants * Add functions to check for I2C bus speed and set it * Add ScreenFonts.h Co-authored-by: Hannes Fuchs * PMSA003I 1st round test * Fix I2C scan speed * Fix minor issues and bring back I2C SPEED def * Remove PMSA003I library as its no longer needed * Add functional SCD4X * Fix screen frame for CO2 * Add admin commands to SCD4X class * Add further admin commands and fixes. * Remove unused I2C speed functions and cleanup * Cleanup of SEN5X specific code added from switching branches * Remove SCAN_I2C_CLOCK_SPEED block as its not needed * Remove associated functions for setting I2C speed * Unify build epoch to add flag in platformio-custom.py (#7917) * Unify build_epoch replacement logic in platformio-custom * Missed one * Fix build error in rak_wismesh_tap_v2 (#7905) In the logs was: "No screen resolution defined in build_flags. Please define DISPLAY_SIZE." set according to similar devices. * Put guards in place around debug heap operations (#7955) * Put guards in place around debug heap operations * Add macros to clean up code * Add pointer as well * Cleanup * Fix memory leak in NextHopRouter: always free packet copy when removing from pending * Formatting * Only queue 2 client notification * Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap * Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap * Update protobufs (#7973) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs * Trunk * Trunk * Static memory pool allocation (#7966) * Static memory pool * Initializer * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs --------- Co-authored-by: WillyJL * Portduino dynamic alloc * Missed * Drop the limit * Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix json report crashes on esp32 (#7978) * Tweak maximums * Fix DRAM overflow on old esp32 targets * Guard bad time warning logs using GPS_DEBUG (#7897) In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy. In combination, these result in a spamming of the logs when a bad time is found When the GPS is active, we're calling the GPS thread every 0.2secs. So this log could be printed 4,500 times in a no-lock scenario :) Reserve this experience for developers using GPS_DEBUG. Fixes https://github.com/meshtastic/firmware/issues/7896 * Scale probe buffer size based on current baud rate (#7975) * Scale probe buffer size based on current baud rate * Throttle bad time validation logging and fix time comparison logic * Remove comment * Missed the other instances * Copy pasta * Fix GPS gm_mktime memory leak (#7981) * Fix overflow of time value (#7984) * Fix overflow of time value * Revert "Fix overflow of time value" This reverts commit 084796920179e80a7500d36c25fd4d82b3ef4214. * That got boogered up * Remove PMSA003 include from modules * Add flag to exclude air quality module * Rework PMSA003I to align with new I2C scanner * Reworks AQ telemetry to match new dynamic allocation method * Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements * Reworks PMSA003I * Move add sensor template to separate file * Split telemetry on screen options * Add variable I2C clock compile flag * Added to Seeed Xiao S3 as demo * Fix drawFrame in AQ module * Module settings override to i2cScan module function * Move to CAN_RECLOCK_I2C per architecture * Add reclock function in TelemetrySensor.cpp * Add flag in ESP32 common * Minor fix * Move I2C reclock function to src/detect * Fix uninitMemberVar errors and compile issue * Make sleep, wakeUp functions generic * Fix STM32 builds * Add exclude AQ sensor to builds that have environmental sensor excludes * Add includes to AddI2CSensorTemplate.h * SEN5X first pass * WIP Sen5X functions * Further (non-working) progress in SEN5X * WIP Sen5X functions * Changes on SEN5X library - removing pm_env as well * Small cleanup of SEN5X sensors * Minor change for SEN5X detection * Remove dup code * Enable PM sensor before sending telemetry. This enables the PM sensor for a predefined period to allow for warmup. Once telemetry is sent, the sensor shuts down again. * Small cleanups in SEN5X sensor * Add dynamic measurement interval for SEN5X * Only disable SEN5X if enough time after reading. * Idle for SEN5X on communication error * Cleanup of logs and remove unnecessary delays * Small TODO * Settle on uint16_t for SEN5X PM data * Make AQTelemetry sensors non-exclusive * Implementation of cleaning in FS prefs and cleanup * Remove unnecessary LOGS * Add cleaning date storage in FS * Report non-cumulative PN * Bring back detection code for SEN5X after branch rebase * Add placeholder for admin message * Add VOC measurements and persistence (WIP) * Adds VOC measurements and state * Still not working on VOC Index persistence * Should it stay in continuous mode? * Add one-shot mode config flag to SEN5X * Add nan checks on sensor data from SEN5X * Working implementation on VOCState * Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h) * Adds conditions for stability and sensor state * Fixes on VOC state and mode swtiching * Adds a new RHT/Gas only mode, with 3600s stabilization time * Fixes the VOCState buffer mismatch * Fixes SEN50/54/55 model mistake * Adapt SEN5X to new sensor list structure. Improve reclock. * Improve reClockI2C conditions for different variants * Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery * Add SEN5X * Fix merge errors * Small reordering in PMS class for consistency * If one sensor fails, AQ telemetry still reports data * Small formatting fix * Add SEN5X to AQI in ScanI2C * SCD4X now part of AQ module with template list * Fixes difference between idle and sleep * In LowPower, sleep is disabled * Requires testing for I2C clock comms for commands * Remove unnecessary import * Add co2 to serialized AQ metrics * Add SFA30 with new sensor template in AQ module * Update library dependencies in platformio.ini * Fix unitialized variables in SEN5X constructor * Fix missing import * Fix uninitMemberVars * Fix import error for SCD4X * Fix I2CClock logic * Fix not reclocking back to 700000Hz * Fix multiple sensors being read simultaneously * The logic in AQ module is different to the one in EnvironmentTelemetryModule. In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data. * Fix pending clock change in PMSA003 * Cleanup of SEN5X class * Exclude AQ sensor from wio-e5 due to flash limitations * Fix I2C clock change logic * Make sure clock is always set to needed value * Fix returns * Fix trunk * Fix on condition in reclock * Fix trunk * Final SFA30 class implementation * Add HCHO to screen and improve logs * Add metrics to mesh packet serializer * Minor fixes in logs * OCD tidy up of logs * Fix sleep function * Remove old I2C_CLOCK_SPEED code --------- Co-authored-by: nikl Co-authored-by: Hannes Fuchs Co-authored-by: Nashui-Yan Co-authored-by: Ben Meadors Co-authored-by: Tom Fifield Co-authored-by: Mike Robbins Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: WillyJL Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 4 + src/configuration.h | 1 + src/detect/ScanI2C.cpp | 2 +- src/detect/ScanI2C.h | 5 +- src/detect/ScanI2CTwoWire.cpp | 9 + src/main.cpp | 1 - src/modules/Telemetry/AirQualityTelemetry.cpp | 44 ++-- src/modules/Telemetry/AirQualityTelemetry.h | 3 +- .../Telemetry/Sensor/PMSA003ISensor.cpp | 4 + src/modules/Telemetry/Sensor/SCD4XSensor.cpp | 2 +- src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 16 +- src/modules/Telemetry/Sensor/SFA30Sensor.cpp | 198 ++++++++++++++++++ src/modules/Telemetry/Sensor/SFA30Sensor.h | 39 ++++ src/serialization/MeshPacketSerializer.cpp | 9 + .../MeshPacketSerializer_nRF52.cpp | 9 + 15 files changed, 320 insertions(+), 26 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/SFA30Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SFA30Sensor.h diff --git a/platformio.ini b/platformio.ini index b7123b865..b9f1e580b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -214,6 +214,8 @@ lib_deps = sensirion/Sensirion Core@0.7.2 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 + # renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x + sensirion/Sensirion I2C SFA3x@1.0.0 ; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) [environmental_extra_no_bsec] @@ -242,3 +244,5 @@ lib_deps = sensirion/Sensirion Core@0.7.2 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 + # renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x + sensirion/Sensirion I2C SFA3x@1.0.0 diff --git a/src/configuration.h b/src/configuration.h index 5d8d764c7..5ec856f88 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -217,6 +217,7 @@ along with this program. If not, see . #define SHTC3_ADDR 0x70 #define LPS22HB_ADDR 0x5C #define LPS22HB_ADDR_ALT 0x5D +#define SFA30_ADDR 0x5D #define SHT31_4x_ADDR 0x44 #define SHT31_4x_ADDR_ALT 0x45 #define PMSA003I_ADDR 0x12 diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index bf01a0365..21aceffd1 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const ScanI2C::FoundDevice ScanI2C::firstAQI() const { - ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X}; + ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X, SFA30}; return firstOfOrNONE(2, types); } diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 528159661..161192766 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -89,8 +89,9 @@ class ScanI2C DA217, CHSC6X, CST226SE, - CW2015, - SEN5X + SEN5X, + SFA30, + CW2015 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index e5e69dca7..2802854ab 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -1,4 +1,6 @@ #include "ScanI2CTwoWire.h" +#include "configuration.h" +#include "detect/ScanI2C.h" #if !MESHTASTIC_EXCLUDE_I2C @@ -456,6 +458,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; case LPS22HB_ADDR_ALT: + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xD060), 48); // get device marking + if (registerValue != 0) { + type = SFA30; + logFoundDevice("SFA30", (uint8_t)addr.address); + break; + } + // TODO - What happens with these two? SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address) SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address) diff --git a/src/main.cpp b/src/main.cpp index 153b9847e..45685f6cd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -681,7 +681,6 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); #endif #ifdef HAS_SDCARD diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index cc1b54373..d7127bb01 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -1,3 +1,4 @@ +#include "DebugConfiguration.h" #include "configuration.h" #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR @@ -25,6 +26,9 @@ #if __has_include() #include "Sensor/SCD4XSensor.h" #endif +#if __has_include() +#include "Sensor/SFA30Sensor.h" +#endif void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { @@ -50,6 +54,9 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::SCD4X); #endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SFA30); +#endif } int32_t AirQualityTelemetryModule::runOnce() @@ -71,7 +78,8 @@ int32_t AirQualityTelemetryModule::runOnce() } if (firstTime) { - // This is the first time the OSThread library has called this function, so do some setup + // This is the first time the OSThread library has called this function, so + // do some setup firstTime = false; if (moduleConfig.telemetry.air_quality_enabled) { @@ -221,6 +229,8 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3"); if (m.has_co2) entries.push_back("CO2: " + String(m.co2) + "ppm"); + if (m.has_form_formaldehyde) + entries.push_back("HCHO: " + String(m.form_formaldehyde) + "ppb"); // === Show first available metric on top-right of first line === if (!entries.empty()) { @@ -256,17 +266,19 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); - LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender, - t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard, - t->variant.air_quality_metrics.pm100_standard); + if (t->variant.air_quality_metrics.has_pm10_standard) + LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, " + "pm100_standard=%i", + sender, t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard, + t->variant.air_quality_metrics.pm100_standard); - // TODO - Decide what to do with these - // LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i", - // t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental, - // t->variant.air_quality_metrics.pm100_environmental); + if (t->variant.air_quality_metrics.has_co2) + LOG_INFO("CO2=%i, CO2_T=%.2f, CO2_H=%.2f", t->variant.air_quality_metrics.co2, + t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity); - LOG_INFO(" | CO2=%i, CO2_T=%f, CO2_H=%f", t->variant.air_quality_metrics.co2, - t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity); + if (t->variant.air_quality_metrics.has_form_formaldehyde) + LOG_INFO("HCHO=%.2f, HCHO_T=%.2f, HCHO_H=%.2f", t->variant.air_quality_metrics.form_formaldehyde, + t->variant.air_quality_metrics.form_temperature, t->variant.air_quality_metrics.form_humidity); #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -354,10 +366,18 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.air_quality_metrics.has_co2_humidity; if (hasAnyCO2) { - LOG_INFO("Send: co2=%i, co2_t=%f, co2_rh=%f", m.variant.air_quality_metrics.co2, + LOG_INFO("Send: co2=%i, co2_t=%.2f, co2_rh=%.2f", m.variant.air_quality_metrics.co2, m.variant.air_quality_metrics.co2_temperature, m.variant.air_quality_metrics.co2_humidity); } + bool hasAnyHCHO = m.variant.air_quality_metrics.has_form_formaldehyde || + m.variant.air_quality_metrics.has_form_temperature || m.variant.air_quality_metrics.has_form_humidity; + + if (hasAnyHCHO) { + LOG_INFO("Send: hcho=%.2f, hcho_t=%.2f, hcho_rh=%.2f", m.variant.air_quality_metrics.form_formaldehyde, + m.variant.air_quality_metrics.form_temperature, m.variant.air_quality_metrics.form_humidity); + } + meshtastic_MeshPacket *p = allocDataProtobuf(m); p->to = dest; p->decoded.want_response = false; @@ -426,4 +446,4 @@ AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule( return result; } -#endif \ No newline at end of file +#endif diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 2b88b74ba..197491f2d 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -64,8 +64,9 @@ class AirQualityTelemetryModule : private concurrency::OSThread, bool firstTime = true; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute + // uint32_t sendToPhoneIntervalMs = 1000; // Send to phone every minute uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; }; -#endif \ No newline at end of file +#endif diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index bc067c04c..a752f2ab8 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -138,6 +138,10 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.air_quality_metrics.has_particles_100um = true; measurement->variant.air_quality_metrics.particles_100um = read16(buffer, 26); + LOG_DEBUG("Got %s readings: pM1p0_standard=%u, pM2p5_standard=%u, pM10p0_standard=%u", sensorName, + measurement->variant.air_quality_metrics.pm10_standard, measurement->variant.air_quality_metrics.pm25_standard, + measurement->variant.air_quality_metrics.pm100_standard); + return true; } diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp index 579d9873b..6572ef9b1 100644 --- a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp @@ -125,7 +125,7 @@ bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement) reClockI2C(currentClock, _bus, false); #endif - LOG_DEBUG("%s readings: %u ppm, %.2f degC, %.2f %rh", sensorName, co2, temperature, humidity); + LOG_DEBUG("Got %s readings: co2=%u, co2_temp=%.2f, co2_hum%.2f", sensorName, co2, temperature, humidity); if (error != SCD4X_NO_ERROR) { LOG_DEBUG("%s: Error while getting measurements: %u", sensorName, error); if (co2 == 0) { diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp index 299a1f7df..0a9db4dff 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -665,16 +665,16 @@ bool SEN5XSensor::readValues() sen5xmeasurement.vocIndex = !isnan(int_vocIndex) ? int_vocIndex / 10.0f : FLT_MAX; sen5xmeasurement.noxIndex = !isnan(int_noxIndex) ? int_noxIndex / 10.0f : FLT_MAX; - LOG_DEBUG("Got: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sen5xmeasurement.pM1p0, sen5xmeasurement.pM2p5, - sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0); + LOG_DEBUG("Got %s readings: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sensorName, sen5xmeasurement.pM1p0, + sen5xmeasurement.pM2p5, sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0); if (model != SEN50) { - LOG_DEBUG("Got: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sen5xmeasurement.humidity, sen5xmeasurement.temperature, - sen5xmeasurement.vocIndex); + LOG_DEBUG("Got %s readings: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sensorName, sen5xmeasurement.humidity, + sen5xmeasurement.temperature, sen5xmeasurement.vocIndex); } if (model == SEN55) { - LOG_DEBUG("Got: noxIndex=%.2f", sen5xmeasurement.noxIndex); + LOG_DEBUG("Got %s readings: noxIndex=%.2f", sensorName, sen5xmeasurement.noxIndex); } return true; @@ -727,9 +727,9 @@ bool SEN5XSensor::readPNValues(bool cumulative) sen5xmeasurement.pN1p0 -= sen5xmeasurement.pN0p5; } - LOG_DEBUG("Got: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f", sen5xmeasurement.pN0p5, - sen5xmeasurement.pN1p0, sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0, sen5xmeasurement.pN10p0, - sen5xmeasurement.tSize); + LOG_DEBUG("Got %s readings: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f", sensorName, + sen5xmeasurement.pN0p5, sen5xmeasurement.pN1p0, sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0, + sen5xmeasurement.pN10p0, sen5xmeasurement.tSize); return true; } diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp new file mode 100644 index 000000000..42a77db40 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp @@ -0,0 +1,198 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../detect/reClockI2C.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SFA30Sensor.h" + +SFA30Sensor::SFA30Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SFA30, "SFA30"){}; + +bool SFA30Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + LOG_INFO("Init sensor: %s", sensorName); + + _bus = bus; + _address = dev->address.address; + +#ifdef SFA30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SFA30_I2C_CLOCK_SPEED */ + + sfa30.begin(*_bus, _address); + delay(20); + + if (this->isError(sfa30.deviceReset())) { +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + state = State::IDLE; + if (this->isError(sfa30.startContinuousMeasurement())) { +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + LOG_INFO("%s starting measurement", sensorName); + +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + status = 1; + state = State::ACTIVE; + measureStarted = getTime(); + LOG_INFO("%s Enabled", sensorName); + + initI2CSensor(); + return true; +}; + +bool SFA30Sensor::isError(uint16_t response) +{ + if (response == SFA30_NO_ERROR) + return false; + + // TODO - Check error to char conversion + LOG_ERROR("%s: %s", sensorName, response); + return true; +} + +void SFA30Sensor::sleep() +{ +#ifdef SFA30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SFA30_I2C_CLOCK_SPEED */ + + // Note - not recommended for this sensor on a periodic basis + if (this->isError(sfa30.stopMeasurement())) { + LOG_ERROR("%s: can't stop measurement"); + }; + +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + LOG_INFO("%s: stop measurement"); + state = State::IDLE; + measureStarted = 0; +} + +uint32_t SFA30Sensor::wakeUp() +{ +#ifdef SFA30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SFA30_I2C_CLOCK_SPEED */ + + LOG_INFO("Waking up %s", sensorName); + if (this->isError(sfa30.startContinuousMeasurement())) { +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return 0; + } + +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + state = State::ACTIVE; + measureStarted = getTime(); + return SFA30_WARMUP_MS; +} + +int32_t SFA30Sensor::wakeUpTimeMs() +{ + return SFA30_WARMUP_MS; +} + +bool SFA30Sensor::canSleep() +{ + // Sleep is disabled in this sensor because readings are not tested with periodic sleep + // with such low power consumption, prefered to keep it active + return false; +} + +bool SFA30Sensor::isActive() +{ + return state == State::ACTIVE; +} + +int32_t SFA30Sensor::pendingForReadyMs() +{ + uint32_t now; + now = getTime(); + uint32_t sinceHchoMeasureStarted = (now - measureStarted) * 1000; + LOG_DEBUG("%s: Since measure started: %ums", sensorName, sinceHchoMeasureStarted); + + if (sinceHchoMeasureStarted < SFA30_WARMUP_MS) { + LOG_INFO("%s: not enough time passed since starting measurement", sensorName); + return SFA30_WARMUP_MS - sinceHchoMeasureStarted; + } + return 0; +} + +bool SFA30Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + float hcho = 0.0; + float humidity = 0.0; + float temperature = 0.0; + +#ifdef SFA30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SFA30_I2C_CLOCK_SPEED */ + + if (this->isError(sfa30.readMeasuredValues(hcho, humidity, temperature))) { + LOG_WARN("%s: No values", sensorName); + return false; + } + +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + measurement->variant.air_quality_metrics.has_form_temperature = true; + measurement->variant.air_quality_metrics.has_form_humidity = true; + measurement->variant.air_quality_metrics.has_form_formaldehyde = true; + + measurement->variant.air_quality_metrics.form_temperature = temperature; + measurement->variant.air_quality_metrics.form_humidity = humidity; + measurement->variant.air_quality_metrics.form_formaldehyde = hcho; + + LOG_DEBUG("Got %s readings: hcho=%.2f, hcho_temp=%.2f, hcho_hum=%.2f", sensorName, hcho, temperature, humidity); + + return true; +} +#endif diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.h b/src/modules/Telemetry/Sensor/SFA30Sensor.h new file mode 100644 index 000000000..9fa9c85fc --- /dev/null +++ b/src/modules/Telemetry/Sensor/SFA30Sensor.h @@ -0,0 +1,39 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "RTC.h" +#include "TelemetrySensor.h" +#include + +#define SFA30_I2C_CLOCK_SPEED 100000 +#define SFA30_WARMUP_MS 10000 +#define SFA30_NO_ERROR 0 + +class SFA30Sensor : public TelemetrySensor +{ + private: + enum class State { IDLE, ACTIVE }; + State state = State::IDLE; + uint32_t measureStarted = 0; + + SensirionI2cSfa3x sfa30; + TwoWire *_bus{}; + uint8_t _address{}; + bool isError(uint16_t response); + + public: + SFA30Sensor(); + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + + virtual bool isActive() override; + virtual void sleep() override; + virtual uint32_t wakeUp() override; + virtual bool canSleep() override; + virtual int32_t wakeUpTimeMs() override; + virtual int32_t pendingForReadyMs() override; +}; + +#endif diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index 042bc3763..819ba3da5 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -158,6 +158,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.air_quality_metrics.has_co2_humidity) { msgPayload["co2_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.co2_humidity); } + if (decoded->variant.air_quality_metrics.has_form_formaldehyde) { + msgPayload["form_formaldehyde"] = new JSONValue(decoded->variant.air_quality_metrics.form_formaldehyde); + } + if (decoded->variant.air_quality_metrics.has_form_temperature) { + msgPayload["form_temperature"] = new JSONValue(decoded->variant.air_quality_metrics.form_temperature); + } + if (decoded->variant.air_quality_metrics.has_form_humidity) { + msgPayload["form_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.form_humidity); + } } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { if (decoded->variant.power_metrics.has_ch1_voltage) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index a0ad4e4b9..bd0a29c51 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -129,6 +129,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.air_quality_metrics.has_co2_humidity) { jsonObj["payload"]["co2_humidity"] = decoded->variant.air_quality_metrics.co2_humidity; } + if (decoded->variant.air_quality_metrics.has_form_formaldehyde) { + jsonObj["payload"]["form_formaldehyde"] = decoded->variant.air_quality_metrics.form_formaldehyde; + } + if (decoded->variant.air_quality_metrics.has_form_temperature) { + jsonObj["payload"]["form_temperature"] = decoded->variant.air_quality_metrics.form_temperature; + } + if (decoded->variant.air_quality_metrics.has_form_humidity) { + jsonObj["payload"]["form_humidity"] = decoded->variant.air_quality_metrics.form_humidity; + } } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { if (decoded->variant.power_metrics.has_ch1_voltage) { jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; From 640dd5f7d7eb7a521dd63aa6b72e3583036aaaf1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:15:38 +0100 Subject: [PATCH 131/223] Upgrade trunk (#9615) --- .trunk/trunk.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 65c0c9107..1c4743935 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,10 +8,10 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.500 - - renovate@43.8.1 + - checkov@3.2.501 + - renovate@43.8.5 - prettier@3.8.1 - - trufflehog@3.93.2 + - trufflehog@3.93.3 - yamllint@1.38.0 - bandit@1.9.3 - trivy@0.69.1 From f86291546de026172a2cb857865d436b6cb11651 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:20:44 +0100 Subject: [PATCH 132/223] chore(deps): update adafruit mpu6050 to v2.2.9 (#9611) --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index bc812dc6f..dbdd6c70e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -144,7 +144,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219 adafruit/Adafruit INA219@1.2.3 # renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050 - adafruit/Adafruit MPU6050@2.2.8 + adafruit/Adafruit MPU6050@2.2.9 # renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH adafruit/Adafruit LIS3DH@1.3.0 # renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0 From bfbce2e31448d5de8948412f3aa71690813fbe3f Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Thu, 12 Feb 2026 06:52:24 -0500 Subject: [PATCH 133/223] Log rxBad PacketHeaders with more info (id, relay_node) like printPacket, so we can try to match RX errors to other packets in the logs. (#9614) --- src/mesh/RadioLibInterface.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 6716109b6..af6ab30c1 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -453,8 +453,11 @@ void RadioLibInterface::handleReceiveInterrupt() } #endif if (state != RADIOLIB_ERR_NONE) { - LOG_ERROR("Ignore received packet due to error=%d (maybe to=0x%08x, from=0x%08x, flags=0x%02x)", state, - radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags); + // Log PacketHeader similar to RadioInterface::printPacket so we can try to match RX errors to other packets in the logs. + LOG_ERROR("Ignore received packet due to error=%d (maybe id=0x%08x fr=0x%08x to=0x%08x flags=0x%02x rxSNR=%g rxRSSI=%i " + "nextHop=0x%x relay=0x%x)", + state, radioBuffer.header.id, radioBuffer.header.from, radioBuffer.header.to, radioBuffer.header.flags, + iface->getSNR(), lround(iface->getRSSI()), radioBuffer.header.next_hop, radioBuffer.header.relay_node); rxBad++; airTime->logAirtime(RX_ALL_LOG, rxMsec); From 8137200cb815e03a022e67954bde69c5fd1b3da6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 05:59:08 -0600 Subject: [PATCH 134/223] chore(deps): update sensirion core to v0.7.3 (#9613) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index dbdd6c70e..307fb052e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -210,7 +210,7 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core - sensirion/Sensirion Core@0.7.2 + sensirion/Sensirion Core@0.7.3 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 @@ -238,6 +238,6 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core - sensirion/Sensirion Core@0.7.2 + sensirion/Sensirion Core@0.7.3 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 From bcc24705a5d29c5bb5a02408914d1a1f3117410e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:10:24 -0600 Subject: [PATCH 135/223] chore(deps): update neopixel to v1.15.4 (#9616) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32/betafpv_2400_tx_micro/platformio.ini | 2 +- variants/esp32c3/heltec_hru_3601/platformio.ini | 2 +- variants/esp32c6/m5stack_unitc6l/platformio.ini | 2 +- variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini | 2 +- variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini | 2 +- variants/esp32s3/esp32-s3-pico/platformio.ini | 2 +- variants/esp32s3/heltec_sensor_hub/platformio.ini | 2 +- variants/esp32s3/unphone/platformio.ini | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/variants/esp32/betafpv_2400_tx_micro/platformio.ini b/variants/esp32/betafpv_2400_tx_micro/platformio.ini index f3d488f43..068d71011 100644 --- a/variants/esp32/betafpv_2400_tx_micro/platformio.ini +++ b/variants/esp32/betafpv_2400_tx_micro/platformio.ini @@ -16,4 +16,4 @@ upload_speed = 460800 lib_deps = ${esp32_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.3 + adafruit/Adafruit NeoPixel@1.15.4 diff --git a/variants/esp32c3/heltec_hru_3601/platformio.ini b/variants/esp32c3/heltec_hru_3601/platformio.ini index 7cf60fc08..aa62fb4fc 100644 --- a/variants/esp32c3/heltec_hru_3601/platformio.ini +++ b/variants/esp32c3/heltec_hru_3601/platformio.ini @@ -7,4 +7,4 @@ build_flags = -I variants/esp32c3/heltec_hru_3601 lib_deps = ${esp32c3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.3 + adafruit/Adafruit NeoPixel@1.15.4 diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index b14fc83f2..37221c103 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -24,7 +24,7 @@ build_unflags = lib_deps = ${esp32c6_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.3 + adafruit/Adafruit NeoPixel@1.15.4 # renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino h2zero/NimBLE-Arduino@2.3.7 build_flags = diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini index 69f3a4af8..9230e08c5 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/platformio.ini @@ -13,7 +13,7 @@ lib_deps = # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 zinggjm/GxEPD2@1.6.7 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.3 + adafruit/Adafruit NeoPixel@1.15.4 build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 diff --git a/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini index 529d9182e..c53831c9f 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini +++ b/variants/esp32s3/diy/my_esp32s3_diy_oled/platformio.ini @@ -11,7 +11,7 @@ upload_speed = 921600 lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.3 + adafruit/Adafruit NeoPixel@1.15.4 build_unflags = ${esp32s3_base.build_unflags} -DARDUINO_USB_MODE=1 diff --git a/variants/esp32s3/esp32-s3-pico/platformio.ini b/variants/esp32s3/esp32-s3-pico/platformio.ini index f24c84aa7..aad29247b 100644 --- a/variants/esp32s3/esp32-s3-pico/platformio.ini +++ b/variants/esp32s3/esp32-s3-pico/platformio.ini @@ -25,4 +25,4 @@ lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2 zinggjm/GxEPD2@1.6.7 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.3 + adafruit/Adafruit NeoPixel@1.15.4 diff --git a/variants/esp32s3/heltec_sensor_hub/platformio.ini b/variants/esp32s3/heltec_sensor_hub/platformio.ini index 3d8fe932d..013280db1 100644 --- a/variants/esp32s3/heltec_sensor_hub/platformio.ini +++ b/variants/esp32s3/heltec_sensor_hub/platformio.ini @@ -11,4 +11,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.3 + adafruit/Adafruit NeoPixel@1.15.4 diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index 045454e8e..f52fcc09a 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -41,7 +41,7 @@ lib_deps = ${esp32s3_base.lib_deps} # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel - adafruit/Adafruit NeoPixel@1.15.3 + adafruit/Adafruit NeoPixel@1.15.4 [env:unphone-tft] board_level = extra From 38c7ad0ed6865fdd7611ffcd1a29c6b425800f1e Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 12 Feb 2026 07:11:12 -0600 Subject: [PATCH 136/223] Exclude status message module --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index b9f1e580b..a20fbe741 100644 --- a/platformio.ini +++ b/platformio.ini @@ -56,6 +56,7 @@ build_flags = -Wno-missing-field-initializers -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 + -DMESHTASTIC_EXCLUDE_STATUS=1 -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage -DLED_BUILTIN=-1 #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now From 1a7f560372bc972d9ce7571f10d4fa30d361a97f Mon Sep 17 00:00:00 2001 From: Wessel Date: Thu, 12 Feb 2026 15:33:57 +0100 Subject: [PATCH 137/223] fix: zero entire public key array instead of only first byte (#9619) --- src/mesh/NodeDB.cpp | 2 +- src/modules/AdminModule.cpp | 2 +- src/modules/NodeInfoModule.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 563c18d6c..caccc18df 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1772,7 +1772,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact) info->has_device_metrics = false; info->has_position = false; info->user.public_key.size = 0; - info->user.public_key.bytes[0] = 0; + memset(info->user.public_key.bytes, 0, sizeof(info->user.public_key.bytes)); } else { /* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with * public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM! diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index a4869f708..419d2b773 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -391,7 +391,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta node->has_device_metrics = false; node->has_position = false; node->user.public_key.size = 0; - node->user.public_key.bytes[0] = 0; + memset(node->user.public_key.bytes, 0, sizeof(node->user.public_key.bytes)); saveChanges(SEGMENT_NODEDATABASE, false); } break; diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index a568505ae..f947a5ac0 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -148,7 +148,7 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() // Strip the public key if the user is licensed if (u.is_licensed && u.public_key.size > 0) { - u.public_key.bytes[0] = 0; + memset(u.public_key.bytes, 0, sizeof(u.public_key.bytes)); u.public_key.size = 0; } From f10d786d823888dbbab55cca073d50f582b043ab Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:15:08 +0100 Subject: [PATCH 138/223] Update protobufs (#9621) --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 3 ++ src/mesh/generated/meshtastic/admin.pb.h | 57 ++++++++++++++++++-- src/mesh/generated/meshtastic/telemetry.pb.h | 8 +-- 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/protobufs b/protobufs index e1a6b3a86..44298d374 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit e1a6b3a868d735da72cd6c94c574d655129d390a +Subproject commit 44298d374fd83cfbc36fdb76c6f966e980cadd93 diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index 01d3fa910..d0bd09800 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -36,6 +36,9 @@ PB_BIND(meshtastic_SCD4X_config, meshtastic_SCD4X_config, AUTO) PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO) +PB_BIND(meshtastic_SCD30_config, meshtastic_SCD30_config, AUTO) + + diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 336510ec3..3dd92997d 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -206,6 +206,27 @@ typedef struct _meshtastic_SEN5X_config { bool set_one_shot_mode; } meshtastic_SEN5X_config; +typedef struct _meshtastic_SCD30_config { + /* Set Automatic self-calibration enabled */ + bool has_set_asc; + bool set_asc; + /* Recalibration target CO2 concentration in ppm (FRC or ASC) */ + bool has_set_target_co2_conc; + uint32_t set_target_co2_conc; + /* Reference temperature in degC */ + bool has_set_temperature; + float set_temperature; + /* Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure) */ + bool has_set_altitude; + uint32_t set_altitude; + /* Power mode for sensor (true for low power, false for normal) */ + bool has_set_measurement_interval; + uint32_t set_measurement_interval; + /* Perform a factory reset of the sensor */ + bool has_soft_reset; + bool soft_reset; +} meshtastic_SCD30_config; + typedef struct _meshtastic_SensorConfig { /* SCD4X CO2 Sensor configuration */ bool has_scd4x_config; @@ -213,6 +234,9 @@ typedef struct _meshtastic_SensorConfig { /* SEN5X PM Sensor configuration */ bool has_sen5x_config; meshtastic_SEN5X_config sen5x_config; + /* SCD30 CO2 Sensor configuration */ + bool has_scd30_config; + meshtastic_SCD30_config scd30_config; } meshtastic_SensorConfig; typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t; @@ -400,6 +424,7 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} @@ -408,9 +433,10 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} -#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default} +#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default, false, meshtastic_SCD30_config_init_default} #define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_SEN5X_config_init_default {false, 0, false, 0} +#define meshtastic_SCD30_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} @@ -418,9 +444,10 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} -#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero} +#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero, false, meshtastic_SCD30_config_init_zero} #define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_SEN5X_config_init_zero {false, 0, false, 0} +#define meshtastic_SCD30_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_AdminMessage_InputEvent_event_code_tag 1 @@ -451,8 +478,15 @@ extern "C" { #define meshtastic_SCD4X_config_set_power_mode_tag 7 #define meshtastic_SEN5X_config_set_temperature_tag 1 #define meshtastic_SEN5X_config_set_one_shot_mode_tag 2 +#define meshtastic_SCD30_config_set_asc_tag 1 +#define meshtastic_SCD30_config_set_target_co2_conc_tag 2 +#define meshtastic_SCD30_config_set_temperature_tag 3 +#define meshtastic_SCD30_config_set_altitude_tag 4 +#define meshtastic_SCD30_config_set_measurement_interval_tag 5 +#define meshtastic_SCD30_config_soft_reset_tag 6 #define meshtastic_SensorConfig_scd4x_config_tag 1 #define meshtastic_SensorConfig_sen5x_config_tag 2 +#define meshtastic_SensorConfig_scd30_config_tag 3 #define meshtastic_AdminMessage_get_channel_request_tag 1 #define meshtastic_AdminMessage_get_channel_response_tag 2 #define meshtastic_AdminMessage_get_owner_request_tag 3 @@ -642,11 +676,13 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4) #define meshtastic_SensorConfig_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) +X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, scd30_config, 3) #define meshtastic_SensorConfig_CALLBACK NULL #define meshtastic_SensorConfig_DEFAULT NULL #define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config #define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config +#define meshtastic_SensorConfig_scd30_config_MSGTYPE meshtastic_SCD30_config #define meshtastic_SCD4X_config_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \ @@ -665,6 +701,16 @@ X(a, STATIC, OPTIONAL, BOOL, set_one_shot_mode, 2) #define meshtastic_SEN5X_config_CALLBACK NULL #define meshtastic_SEN5X_config_DEFAULT NULL +#define meshtastic_SCD30_config_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \ +X(a, STATIC, OPTIONAL, UINT32, set_target_co2_conc, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 3) \ +X(a, STATIC, OPTIONAL, UINT32, set_altitude, 4) \ +X(a, STATIC, OPTIONAL, UINT32, set_measurement_interval, 5) \ +X(a, STATIC, OPTIONAL, BOOL, soft_reset, 6) +#define meshtastic_SCD30_config_CALLBACK NULL +#define meshtastic_SCD30_config_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_AdminMessage_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg; @@ -675,6 +721,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; extern const pb_msgdesc_t meshtastic_SensorConfig_msg; extern const pb_msgdesc_t meshtastic_SCD4X_config_msg; extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; +extern const pb_msgdesc_t meshtastic_SCD30_config_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg @@ -687,6 +734,7 @@ extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; #define meshtastic_SensorConfig_fields &meshtastic_SensorConfig_msg #define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg #define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg +#define meshtastic_SCD30_config_fields &meshtastic_SCD30_config_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size @@ -696,9 +744,10 @@ extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 +#define meshtastic_SCD30_config_size 27 #define meshtastic_SCD4X_config_size 29 #define meshtastic_SEN5X_config_size 7 -#define meshtastic_SensorConfig_size 40 +#define meshtastic_SensorConfig_size 69 #define meshtastic_SharedContact_size 127 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 7c7ae457a..8c34cd84c 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -109,7 +109,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* STH21 Temperature and R. Humidity sensor */ meshtastic_TelemetrySensorType_SHT21 = 47, /* Sensirion STC31 CO2 sensor */ - meshtastic_TelemetrySensorType_STC31 = 48 + meshtastic_TelemetrySensorType_STC31 = 48, + /* SCD30 CO2, humidity, temperature sensor */ + meshtastic_TelemetrySensorType_SCD30 = 49 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -487,8 +489,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_STC31 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_STC31+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SCD30 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SCD30+1)) From c3321771efa80bc776f74fe103080c767e7856ce Mon Sep 17 00:00:00 2001 From: Tom <116762865+NomDeTom@users.noreply.github.com> Date: Thu, 12 Feb 2026 19:20:54 +0000 Subject: [PATCH 139/223] Xiao NRF - define suitable i2c pins for the sub-variants (#8866) Co-authored-by: Christian Walther --- .../seeed-xiao-nrf52840-wio-sx1262/README.md | 44 ++--- .../platformio.ini | 18 +- .../variant.cpp | 62 ------ .../seeed-xiao-nrf52840-wio-sx1262/variant.h | 183 ------------------ .../seeed_xiao_nrf52840_e22/platformio.ini | 6 +- variants/nrf52840/diy/xiao_ble/platformio.ini | 52 ++++- .../seeed_xiao_nrf52840_kit/platformio.ini | 13 +- .../seeed_xiao_nrf52840_kit/variant.h | 114 ++++++++--- 8 files changed, 170 insertions(+), 322 deletions(-) delete mode 100644 variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp delete mode 100644 variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md index 194c53434..7fbf83d7c 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md @@ -1,43 +1,21 @@ # XIAO nRF52840 + XIAO Wio SX1262 -For a mere doubling in price you too can swap out the XIAO ESP32C3 for a XIAO nRF52840, stack the Wio SX1262 radio board either above or underneath the nRF52840, solder the pins, and achieve a massive improvement in battery life! +For a mere doubling in price you too can swap out the XIAO ESP32S3 for a XIAO nRF52840, stack the Wio SX1262 radio board either above or underneath the nRF52840, solder the pins, and achieve a massive improvement in battery life! -I'm not really sure why else you would want to as the ESP32C3 is perfectly cromulent, easily connects to the Wio SX1262 via the B2B connector and has an onboard IPEX connector for the included Bluetooth antenna. So you'll also lose BT range, but you will also have working ADC for the battery in Meshtastic and also have an ESP32C3 to use for something else! +I'm not really sure why else you would want to as the ESP32S3 is perfectly cromulent, easily connects to the Wio SX1262 via the B2B connector and has an onboard IPEX connector for the included Bluetooth antenna. So you'll also lose BT range, but you will also have working ADC for the battery in Meshtastic and also have an ESP32S3 to use for something else! If you're still reading you are clearly gonna do it anyway, so...mount the Wio SX1262 either on top or underneath depending on your preference. The `variant.h` will work with either configuration though it does map the Wio SX1262's button to nRF52840 Pin `D5` as it can still be used as a user button and it's nice to be able to gracefully shutdown a node by holding it down for 5 seconds. If you do decide to wire up the button, orient it so looking straight-down at the Wio SX1262 the radio chip is at the bottom, button in the middle and the hole is at the top - the **left** side of the button should be soldered to `GND` (e.g. the 2nd pin down the top on the **right** row of pins) and the **right** side of the button should be soldered to `D5` (e.g. the 2nd pin up from the button on the **left** row of pins.). This mirrors the original wiring and wiring it in reverse could end up connecting GND to voltage and that's no beuno. -Serial Pins remain available on `D6` (TX) and `D7` (RX) should you want to use them, The same pins could be repurposed for `i2c` if you would like to have that instead of serial, in `variant.h` you would just need to change: +Serial Pins remain available on `D6` (TX) and `D7` (RX) should you want to use them, and I2C has been mapped to NFC1 (SDA, D30) and NFC2 (SCL, D31) -```c++ -// RX and TX pins -#define PIN_SERIAL1_RX (6) -#define PIN_SERIAL1_TX (7) +The same pins could be reordered if you would like to have a different arrangement, in `variant.h` you would just need to change the relevant lines: + +```cpp +#define GPS_TX_PIN D6 // This is data from the MCU +#define GPS_RX_PIN D7 // This is data from the GNSS module + +#define PIN_WIRE_SDA D6 +#define PIN_WIRE_SCL D7 ``` - -to - -```c++ -// RX and TX pins -#define PIN_SERIAL1_RX (-1) -#define PIN_SERIAL1_TX (-1) -``` - -and - -```c++ -#define PIN_WIRE_SDA (-1) -#define PIN_WIRE_SCL (-1) -// #define PIN_WIRE_SDA (6) -// #define PIN_WIRE_SCL (7) -``` - -to - -```c++ -#define PIN_WIRE_SDA (6) -#define PIN_WIRE_SCL (7) -``` - -If you wanted both serial and i2c you could even go so far as to use the pads for the PDM mic which is missing on the non-sense board (`P1.00` / `P0.16`)... or move up to the nRF52840 Plus which has even more pins available but hasn't been checked/confirmed if it follows the same pin mapping as the non-plus. diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini index 10eab2aa4..81076bd55 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini @@ -1,13 +1,17 @@ -; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY -[env:seeed-xiao-nrf52840-wio-sx1262] -board = xiao_ble_sense -extends = nrf52840_base +; Seeed Xiao BLE but using the B2B from ESP32S3 variant +[env:seeed_xiao_nrf52840_btb] +extends = env:seeed_xiao_nrf52840_kit board_level = extra build_flags = ${nrf52840_base.build_flags} - -Ivariants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262 - -D PRIVATE_HW + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 + -DPRIVATE_HW ; Define private hardware + -DSEEED_XIAO_NRF_WIO_BTB ; Define Seeed XIAO nRF Wio B2B + -USEEED_XIAO_NRF52840_KIT ; Remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink \ No newline at end of file diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp deleted file mode 100644 index 300f69d0b..000000000 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // D0 .. D13 - 2, // D0 is P0.02 (A0) - 3, // D1 is P0.03 (A1) - 28, // D2 is P0.28 (A2) - 29, // D3 is P0.29 (A3) - 4, // D4 is P0.04 (A4,SDA) - 5, // D5 is P0.05 (A5,SCL) - 43, // D6 is P1.11 (TX) - 44, // D7 is P1.12 (RX) - 45, // D8 is P1.13 (SCK) - 46, // D9 is P1.14 (MISO) - 47, // D10 is P1.15 (MOSI) - - // LEDs - 26, // D11 is P0.26 (LED RED) - 6, // D12 is P0.06 (LED BLUE) - 30, // D13 is P0.30 (LED GREEN) - 14, // D14 is P0.14 (READ_BAT) - - // LSM6DS3TR - 40, // D15 is P1.08 (6D_PWR) - 27, // D16 is P0.27 (6D_I2C_SCL) - 7, // D17 is P0.07 (6D_I2C_SDA) - 11, // D18 is P0.11 (6D_INT1) - - // MIC - 42, // 17,//42, // D19 is P1.10 (MIC_PWR) - 32, // 26,//32, // D20 is P1.00 (PDM_CLK) - 16, // 25,//16, // D21 is P0.16 (PDM_DATA) - - // BQ25100 - 13, // D22 is P0.13 (HICHG) - 17, // D23 is P0.17 (~CHG) - - // - 21, // D24 is P0.21 (QSPI_SCK) - 25, // D25 is P0.25 (QSPI_CSN) - 20, // D26 is P0.20 (QSPI_SIO_0 DI) - 24, // D27 is P0.24 (QSPI_SIO_1 DO) - 22, // D28 is P0.22 (QSPI_SIO_2 WP) - 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) - - // NFC - 9, // D30 is P0.09 (NFC1) - 10, // D31 is P0.10 (NFC2) - - // VBAT - 31, // D32 is P0.10 (VBAT) -}; - -void initVariant() -{ - // Set BQ25101 ISET to 100mA instead of 50mA - pinMode(HICHG, OUTPUT); - digitalWrite(HICHG, LOW); -} diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h deleted file mode 100644 index c132eba5b..000000000 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ /dev/null @@ -1,183 +0,0 @@ -// basically xiao_ble with pins remapped for: -// Seeed XIAO nRF52840 : https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html -// Seeed Wio SX1626 : https://www.seeedstudio.com/Wio-SX1262-with-XIAO-ESP32S3-p-5982.html - -#ifndef _SEEED_XIAO_NRF52840_SENSE_H_ -#define _SEEED_XIAO_NRF52840_SENSE_H_ - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -#define USE_LFXO // Board uses 32khz crystal for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#define PINS_COUNT (33) -#define NUM_DIGITAL_PINS (33) -#define NUM_ANALOG_INPUTS (8) // A6 is used for battery, A7 is analog reference -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -// ---- -#define LED_RED 11 -#define LED_BLUE 12 -#define LED_GREEN 13 - -#define PIN_LED1 LED_GREEN -#define PIN_LED2 LED_BLUE -#define PIN_LED3 LED_RED - -#define LED_STATE_ON 1 // State when LED is lit - -// XIAO Wio-SX1262 Shield User button -#define PIN_BUTTON1 5 -#define BUTTON_NEED_PULLUP - -// Digital Pins -// ------------ -#define D0 (0ul) -#define D1 (1ul) -#define D2 (2ul) -#define D3 (3ul) -#define D4 (4ul) -#define D5 (5ul) -#define D6 (6ul) -#define D7 (7ul) -#define D8 (8ul) -#define D9 (9ul) -#define D10 (10ul) - -// Analog Pins -// ----------- -#define PIN_A0 (0) -#define PIN_A1 (1) -#define PIN_A2 (2) -#define PIN_A3 (3) -#define PIN_A4 (4) -#define PIN_A5 (5) -#define PIN_VBAT (32) -#define VBAT_ENABLE (14) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -#define ADC_RESOLUTION 12 - -// Other Pins -// ---------- -#define PIN_NFC1 (30) -#define PIN_NFC2 (31) - -// RX and TX pins -#define PIN_SERIAL1_RX (-1) -#define PIN_SERIAL1_TX (-1) -// complains if not defined -#define PIN_SERIAL2_RX (-1) -#define PIN_SERIAL2_TX (-1) - -// 4 is used as RF_SW and 5 for USR button so... -#define PIN_WIRE_SDA (6) -#define PIN_WIRE_SCL (7) - -static const uint8_t SDA = PIN_WIRE_SDA; -static const uint8_t SCL = PIN_WIRE_SCL; - -// SPI SX1262 -// ---------- -#define SPI_SX1262 -#ifdef SPI_SX1262 -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (9) -#define PIN_SPI_MOSI (10) -#define PIN_SPI_SCK (8) - -static const uint8_t SS = D3; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -// supported modules list -#define USE_SX1262 - -// common pinouts for SX126X modules -#define SX126X_CS D3 -#define SX126X_DIO1 D0 -#define SX126X_BUSY D1 -#define SX126X_RESET D2 - -// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_RXEN 38 -#define SX126X_TXEN RADIOLIB_NC -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#endif - -// Wire Interfaces -// ------------------- -#define WIRE_INTERFACES_COUNT 1 // 2 - -// Sense version has IMU and PDM Mic -// #define XIAO_SENSE -#ifndef XIAO_SENSE -// 6 DoF IMU -#define PIN_LSM6DS3TR_C_POWER (15) -#define PIN_LSM6DS3TR_C_INT1 (18) -// PDM Interfaces -// --------------- -#define PIN_PDM_PWR (19) -#define PIN_PDM_CLK (20) -#define PIN_PDM_DIN (21) -#endif - -// QSPI Pins -// --------- -#define PIN_QSPI_SCK (24) -#define PIN_QSPI_CS (25) -#define PIN_QSPI_IO0 (26) -#define PIN_QSPI_IO1 (27) -#define PIN_QSPI_IO2 (28) -#define PIN_QSPI_IO3 (29) - -// On-board QSPI Flash -// ------------------- -#define EXTERNAL_FLASH_DEVICES P25Q16H -#define EXTERNAL_FLASH_USE_QSPI - -// Battery -// ------- -// P0_14 = 14 Reads battery voltage from divider on signal board. -// PIN_VBAT is reading voltage divider on XIAO and is program pin 32 / or P0.31 -#define ADC_CTRL VBAT_ENABLE -#define ADC_CTRL_ENABLED LOW -#define BATTERY_SENSE_RESOLUTION_BITS 10 -#define CHARGE_LED 23 // P0_17 = 17 D23 YELLOW CHARGE LED -#define HICHG 22 // P0_13 = 13 D22 Charge-select pin for Lipo for 100 mA instead of default 50mA charge - -// The battery sense is hooked to pin A0 (5) -#define BATTERY_PIN PIN_VBAT // PIN_A0 - -// ratio of voltage divider = 3.0 (R17=1M, R18=510k) -#define ADC_MULTIPLIER 3 // 3.0 + a bit for being optimistic - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif \ No newline at end of file diff --git a/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini b/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini index a5d0aaf8f..c923bbdb7 100644 --- a/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini +++ b/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini @@ -6,7 +6,8 @@ build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M30S -build_unflags = -DGPS_L76K + -USEEED_XIAO_NRF52840_KIT ; remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define ; Seeed XIAO nRF52840 + EBYTE E22-900M33S - Pinout matching Wio-SX1262 (SKU 113010003) [env:seeed_xiao_nrf52840_e22_900m33s] @@ -16,4 +17,5 @@ build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M33S -build_unflags = -DGPS_L76K + -USEEED_XIAO_NRF52840_KIT ; remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define \ No newline at end of file diff --git a/variants/nrf52840/diy/xiao_ble/platformio.ini b/variants/nrf52840/diy/xiao_ble/platformio.ini index 6c764ea78..42f53f1bf 100644 --- a/variants/nrf52840/diy/xiao_ble/platformio.ini +++ b/variants/nrf52840/diy/xiao_ble/platformio.ini @@ -2,9 +2,55 @@ [env:xiao_ble] extends = env:seeed_xiao_nrf52840_kit board_level = extra -build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 -D PRIVATE_HW -DXIAO_BLE_LEGACY_PINOUT -DEBYTE_E22 - -DEBYTE_E22_900M30S -build_unflags = -DGPS_L76K + -USEEED_XIAO_NRF52840_KIT ; remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink + +; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893 +[env:xiao_ble_30db] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DPRIVATE_HW ; Define private hardware + -DXIAO_BLE_LEGACY_PINOUT ; Set legacy pinout + -DEBYTE_E22_900M30S ; Set 30db module + -USEEED_XIAO_NRF52840_KIT ; Remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink + +; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893 +[env:xiao_ble_33db] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DPRIVATE_HW ; Define private hardware + -DXIAO_BLE_LEGACY_PINOUT ; Set legacy pinout + -DEBYTE_E22_900M33S ; Set 33db module + -USEEED_XIAO_NRF52840_KIT ; Remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink \ No newline at end of file diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini index 68be47622..3f9a4f7af 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini @@ -13,21 +13,22 @@ extends = nrf52840_base board = xiao_ble_sense board_level = pr build_flags = ${nrf52840_base.build_flags} - -Ivariants/nrf52840/seeed_xiao_nrf52840_kit - -Isrc/platform/nrf52/softdevice - -Isrc/platform/nrf52/softdevice/nrf52 + -I variants/nrf52840/seeed_xiao_nrf52840_kit + -I src/platform/nrf52/softdevice + -I src/platform/nrf52/softdevice/nrf52 -DSEEED_XIAO_NRF52840_KIT - -DGPS_L76K + -DSEEED_XIAO_NRF_KIT_DEFAULT board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink -; Seeed Xiao BLE but with GPS undefined, and therefore i2c active +; Seeed Xiao BLE but with GPS moved to NFC pins, and therefore i2c active [env:seeed_xiao_nrf52840_kit_i2c] extends = env:seeed_xiao_nrf52840_kit board_level = extra build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -DSEEED_XIAO_NRF52840_KIT -build_unflags = -DGPS_L76K + -DSEEED_XIAO_NRF_KIT_I2C ; Define I2C variant + -USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define \ No newline at end of file diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index 8615ca22e..4dc28557d 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -17,6 +17,37 @@ extern "C" { #endif // __cplusplus +/* +Xiao pin assignments + +| Pin | Default | I2C | BTB | BLE-L | | Pin | Default | I2C | BTB | BLE-L | +| ----- | -------- | ---- | ---- | ----- | --- | ----- | ------- | ---- | ---- | ----- | +| | | | | | | | | | | | +| D0 | G_STBY | UBTN | DIO1 | CS | | 5v | | | | | +| D1 | DIO1 | DIO1 | Busy | DIO1 | | GND | | | | | +| D2 | NRST | NRST | NRST | Busy | | 3v3 | | | | | +| D3 | Busy | Busy | CS | NRST | | D10 | MOSI | MOSI | MOSI | MOSI | +| D4 | CS | CS | RXEN | SDA | | D9 | MISO | MISO | MISO | MISO | +| D5 | RXEN | RXEN | | SCL | | D8 | SCK | SCK | SCK | SCK | +| D6 | G_TX | SDA | G_TX | | | D7 | G_RX | SCL | G_RX | RXEN | +| | | | | | | | | | | | +| | End | | | | | | | | | | +| NFC1/ | SDA | G_TX | SDA | G_TX | | NFC2/ | SCL | G_RX | SCL | G_RX | +| D30 | | | | | | D31 | | | | | +| | | | | | | | | | | | +| | Internal | | | | | | | | | | +| D16 | SCL1 | SCL1 | SCL1 | SCL1 | | | | | | | +| D17 | SDA1 | SDA1 | SDA1 | SDA1 | | | | | | | + +The default column shows the pin assignments for the Wio-SX1262 for XIAO +(standalone SKU 113010003 or nRF52840 kit SKU 102010710). +The I2C column shows an alternative pin assignment using I2C on D6/D7 in place of the GNSS. +The BTB column shows the pin assignment for the Wio-SX1262 -30-pin board-to-board connector version from the ESP32S3 kit. +The BLE-L column shows the pin assignment for the original DIY xiao_ble, and which is retained for legacy users. +Note that the in addition to the difference between the default and the I2C pinouts in placing the pins on NFC or +D6/D7, the user button is activated on D0. The button conflicts with the official GNSS module, so caution is advised. +*/ + #define PINS_COUNT (33) #define NUM_DIGITAL_PINS (33) #define NUM_ANALOG_INPUTS (8) @@ -91,15 +122,15 @@ static const uint8_t A5 = PIN_A5; */ #define USE_SX1262 -#ifdef XIAO_BLE_LEGACY_PINOUT +#if defined(XIAO_BLE_LEGACY_PINOUT) // Legacy xiao_ble variant pinout for third-party SX126x modules e.g. EBYTE E22 #define SX126X_CS D0 #define SX126X_DIO1 D1 #define SX126X_BUSY D2 #define SX126X_RESET D3 #define SX126X_RXEN D7 - -#elif defined(SEEED_XIAO_WIO_BTB) +#else +#if defined(SEEED_XIAO_NRF_WIO_BTB) // Wio-SX1262 for XIAO with 30-pin board-to-board connector // https://files.seeedstudio.com/products/SenseCAP/Wio_SX1262/Schematic_Diagram_Wio-SX1262_for_XIAO.pdf #define SX126X_CS D3 @@ -109,13 +140,15 @@ static const uint8_t A5 = PIN_A5; #define SX126X_RXEN D4 #else // Wio-SX1262 for XIAO (standalone SKU 113010003 or nRF52840 kit SKU 102010710) +// Same for both default and I2C pinouts // https://files.seeedstudio.com/products/SenseCAP/Wio_SX1262/Wio-SX1262%20for%20XIAO%20V1.0_SCH.pdf #define SX126X_CS D4 #define SX126X_DIO1 D1 #define SX126X_BUSY D3 #define SX126X_RESET D2 #define SX126X_RXEN D5 -#endif +#endif // defined(SEEED_XIAO_NRF_WIO_BTB) +#endif // defined(XIAO_BLE_LEGACY_PINOUT) // Common pinouts for all SX126x pinouts above #define SX126X_TXEN RADIOLIB_NC @@ -141,18 +174,26 @@ static const uint8_t SCK = PIN_SPI_SCK; * GPS */ // GPS L76K -#ifdef GPS_L76K + +// Default GPS L76K +#if defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB) +#define GPS_L76K #define GPS_TX_PIN D6 // This is data from the MCU #define GPS_RX_PIN D7 // This is data from the GNSS module +#if defined(SEEED_XIAO_NRF_KIT_DEFAULT) +#define PIN_GPS_STANDBY D0 // this is where the conflicting pinouts come from +#endif +// I2C and BLE-Legacy put them on the NFC pins +#else +#define GPS_TX_PIN (30) +#define GPS_RX_PIN (31) +#endif + #define HAS_GPS 1 +#define GPS_BAUDRATE 9600 #define GPS_THREAD_INTERVAL 50 #define PIN_SERIAL1_TX GPS_TX_PIN #define PIN_SERIAL1_RX GPS_RX_PIN -#define PIN_GPS_STANDBY D0 -#else -#define PIN_SERIAL1_RX (-1) -#define PIN_SERIAL1_TX (-1) -#endif /* * Battery @@ -171,39 +212,60 @@ static const uint8_t SCK = PIN_SPI_SCK; * Wire Interfaces * Keep this section after potentially conflicting pin definitions */ -#define I2C_NO_RESCAN // I2C is a bit finicky, don't scan too much -#define WIRE_INTERFACES_COUNT 1 +#define I2C_NO_RESCAN // I2C is a bit finicky, don't scan too much +#define WIRE_INTERFACES_COUNT 1 // changed to 1 for now, as LSM6DS3TR has issues. #if defined(XIAO_BLE_LEGACY_PINOUT) // Used for I2C by DIY xiao_ble variant #define PIN_WIRE_SDA D4 #define PIN_WIRE_SCL D5 -#elif !defined(GPS_L76K) -// If D6 and D7 are free, I2C is probably the most versatile assignment +#else +// Put the I2C pins on the NFC pins by default +#if defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB) +#define PIN_WIRE_SDA 30 +#define PIN_WIRE_SCL 31 +#else +// If not on legacy or defauly, we're wanting I2C on the back pins #define PIN_WIRE_SDA D6 #define PIN_WIRE_SCL D7 -#else -// Internal LSM6DS3TR on XIAO nRF52840 Series -#define PIN_WIRE_SDA (17) -#define PIN_WIRE_SCL (16) -#endif +#endif // defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB) +#endif // defined(XIAO_BLE_LEGACY_PINOUT) -static const uint8_t SDA = PIN_WIRE_SDA; -static const uint8_t SCL = PIN_WIRE_SCL; +// // Internal LSM6DS3TR on XIAO nRF52840 Series - put it on wire1 +// // Note: disabled for now, as there are some issues with the LSM. +// #define PIN_WIRE1_SDA (17) +// #define PIN_WIRE1_SCL (16) + +static const uint8_t SDA = PIN_WIRE_SDA; // Not sure if this is needed +static const uint8_t SCL = PIN_WIRE_SCL; // Not sure if this is needed + +// // QSPI Pins +// // --------- +// #define PIN_QSPI_SCK (24) +// #define PIN_QSPI_CS (25) +// #define PIN_QSPI_IO0 (26) +// #define PIN_QSPI_IO1 (27) +// #define PIN_QSPI_IO2 (28) +// #define PIN_QSPI_IO3 (29) + +// // On-board QSPI Flash +// // ------------------- +// #define EXTERNAL_FLASH_DEVICES P25Q16H +// #define EXTERNAL_FLASH_USE_QSPI /* * Buttons * Keep this section after potentially conflicting pin definitions * because D0 has multiple possible conflicts with various XIAO modules: - * - PIN_GPS_STANDBY on the L76K GNSS Module - * - DIO1 on the Wio-SX1262 - 30-pin board-to-board connector version - * - SX1262X CS on XIAO BLE legacy pinout */ - -#if !defined(GPS_L76K) && !defined(SEEED_XIAO_WIO_BTB) && !defined(XIAO_BLE_LEGACY_PINOUT) +#if defined(SEEED_XIAO_NRF_KIT_I2C) #define BUTTON_PIN D0 #endif +#if defined(SEEED_XIAO_NRF_WIO_BTB) +#define BUTTON_PIN D5 +#endif + #ifdef __cplusplus } #endif From d5bde83ff5f7ab641892fe7855ac0d05c856d56d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 12:29:43 -0600 Subject: [PATCH 140/223] Upgrade trunk (#9631) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 1c4743935..03a60cad0 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,14 +9,14 @@ plugins: lint: enabled: - checkov@3.2.501 - - renovate@43.8.5 + - renovate@43.10.3 - prettier@3.8.1 - trufflehog@3.93.3 - yamllint@1.38.0 - bandit@1.9.3 - trivy@0.69.1 - taplo@0.10.0 - - ruff@0.15.0 + - ruff@0.15.1 - isort@7.0.0 - markdownlint@0.47.0 - oxipng@10.1.0 From 947f8176dc960f4f174d9e4e972c5b09412380ed Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 13 Feb 2026 17:34:31 -0600 Subject: [PATCH 141/223] Update src/detect/ScanI2C.cpp Co-authored-by: Wessel --- src/detect/ScanI2C.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 21aceffd1..3389cce91 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -44,7 +44,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const ScanI2C::FoundDevice ScanI2C::firstAQI() const { ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X, SFA30}; - return firstOfOrNONE(2, types); + return firstOfOrNONE(4, types); } ScanI2C::FoundDevice ScanI2C::firstRGBLED() const From 3d649d518f5ddb68ab11c3bb7f09dc7e814253e1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 13 Feb 2026 17:34:53 -0600 Subject: [PATCH 142/223] Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp Co-authored-by: Wessel --- src/modules/Telemetry/Sensor/SFA30Sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp index 42a77db40..835a2340b 100644 --- a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp @@ -84,7 +84,7 @@ void SFA30Sensor::sleep() // Note - not recommended for this sensor on a periodic basis if (this->isError(sfa30.stopMeasurement())) { - LOG_ERROR("%s: can't stop measurement"); + LOG_ERROR("%s: can't stop measurement", sensorName); }; #if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) From c79e1925d95a0c9676aec7e873c7a682ac869b45 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 13 Feb 2026 17:35:03 -0600 Subject: [PATCH 143/223] Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp Co-authored-by: Wessel --- src/modules/Telemetry/Sensor/SFA30Sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp index 835a2340b..023298a28 100644 --- a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp @@ -91,7 +91,7 @@ void SFA30Sensor::sleep() reClockI2C(currentClock, _bus, false); #endif - LOG_INFO("%s: stop measurement"); + LOG_INFO("%s: stop measurement", sensorName); state = State::IDLE; measureStarted = 0; } From 71edf2fb48f2202f9f0c6d6c68615476a1a6b5fb Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 13 Feb 2026 17:35:13 -0600 Subject: [PATCH 144/223] Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp Co-authored-by: Wessel --- src/modules/Telemetry/Sensor/SFA30Sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp index 023298a28..c5b5845d9 100644 --- a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp +++ b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp @@ -65,7 +65,7 @@ bool SFA30Sensor::isError(uint16_t response) return false; // TODO - Check error to char conversion - LOG_ERROR("%s: %s", sensorName, response); + LOG_ERROR("%s: %u", sensorName, response); return true; } From d770ef27c705a220aacdaf562f58897dbe1e389f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 13 Feb 2026 17:36:12 -0600 Subject: [PATCH 145/223] Update src/mesh/NodeDB.cpp Co-authored-by: Wessel --- src/mesh/NodeDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index caccc18df..913d45b41 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1415,7 +1415,7 @@ void NodeDB::loadFromDisk() moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0'; } if (portduino_config.enable_UDP) { - config.network.enabled_protocols = true; + config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST; } #endif From df145615608d89dc8d36c62975c05edd88007d1d Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 14 Feb 2026 19:12:59 +0100 Subject: [PATCH 146/223] convert GPS global and some new in gps.cpp to unique_ptr (#9628) Trying this to see if anything bad happen if I were to replace most raw pointers with unique_ptr. I didn't used std::make_unique since it is only supported in C++14 and onwards but until we update esp32 to arduino 3.x the ESP32 xtensa chips use a C++11 std. --- src/gps/GPS.cpp | 22 ++++++++++------------ src/gps/GPS.h | 6 ++++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 13e5c32d1..58a512da1 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -52,7 +52,7 @@ SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT; HardwareSerial *GPS::_serial_gps = nullptr; #endif -GPS *gps = nullptr; +std::unique_ptr gps = nullptr; static GPSUpdateScheduling scheduling; @@ -127,7 +127,7 @@ static int32_t gpsSwitch() return 1000; } -static concurrency::Periodic *gpsPeriodic; +static str::unique_ptr gpsPeriodic; #endif static void UBXChecksum(uint8_t *message, size_t length) @@ -1485,7 +1485,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector 2048) bufferSize = 2048; - char *response = new char[bufferSize](); // Dynamically allocate based on baud rate + auto response = std::unique_ptr(new char[bufferSize]); // Dynamically allocate based on baud rate uint16_t responseLen = 0; unsigned long start = millis(); while (millis() - start < timeout) { @@ -1501,19 +1501,18 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) { // check if we can see our chips for (const auto &chipInfo : responseMap) { - if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) { + if (strstr(response.get(), chipInfo.detectionString.c_str()) != nullptr) { #ifdef GPS_DEBUG - LOG_DEBUG(response); + LOG_DEBUG(response.get()); #endif LOG_INFO("%s detected", chipInfo.chipName.c_str()); - delete[] response; // Cleanup before return return chipInfo.driver; } } } if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') { #ifdef GPS_DEBUG - LOG_DEBUG(response); + LOG_DEBUG(response.get()); #endif // Reset the response buffer for the next potential message responseLen = 0; @@ -1522,13 +1521,12 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector GPS::createGps() { int8_t _rx_gpio = config.position.rx_gpio; int8_t _tx_gpio = config.position.tx_gpio; @@ -1553,7 +1551,7 @@ GPS *GPS::createGps() if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all return nullptr; - GPS *new_gps = new GPS; + auto new_gps = std::unique_ptr(new GPS()); new_gps->rx_gpio = _rx_gpio; new_gps->tx_gpio = _tx_gpio; @@ -1581,7 +1579,7 @@ GPS *GPS::createGps() #ifdef PIN_GPS_SWITCH // toggle GPS via external GPIO switch pinMode(PIN_GPS_SWITCH, INPUT); - gpsPeriodic = new concurrency::Periodic("GPSSwitch", gpsSwitch); + gpsPeriodic = std::unique_ptr(new concurrency::Periodic("GPSSwitch", gpsSwitch)); #endif // Currently disabled per issue #525 (TinyGPS++ crash bug) diff --git a/src/gps/GPS.h b/src/gps/GPS.h index fcbf361d5..8d63ce82f 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -2,6 +2,8 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_GPS +#include + #include "GPSStatus.h" #include "GpioLogic.h" #include "Observer.h" @@ -118,7 +120,7 @@ class GPS : private concurrency::OSThread // Creates an instance of the GPS class. // Returns the new instance or null if the GPS is not present. - static GPS *createGps(); + static std::unique_ptr createGps(); // Wake the GPS hardware - ready for an update void up(); @@ -256,5 +258,5 @@ class GPS : private concurrency::OSThread uint8_t fixeddelayCtr = 0; }; -extern GPS *gps; +extern std::unique_ptr gps; #endif // Exclude GPS From e1f9ccd33707e2607182ac3afb66d77281f51ffc Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 14 Feb 2026 19:32:16 +0100 Subject: [PATCH 147/223] replace delete in RedirectablePrint.cpp with std::unique_ptr (#9642) Is part of the unique_ptr modernization effort. --- src/RedirectablePrint.cpp | 47 +++++++++++++++------------------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index e15d56912..672c8334c 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -227,34 +227,21 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); #endif if (isBleConnected) { - char *message; - size_t initialLen; - size_t len; - initialLen = strlen(format); - message = new char[initialLen + 1]; - len = vsnprintf(message, initialLen + 1, format, arg); - if (len > initialLen) { - delete[] message; - message = new char[len + 1]; - vsnprintf(message, len + 1, format, arg); - } auto thread = concurrency::OSThread::currentThread; meshtastic_LogRecord logRecord = meshtastic_LogRecord_init_zero; logRecord.level = getLogLevel(logLevel); - strcpy(logRecord.message, message); + vsprintf(logRecord.message, format, arg); if (thread) strcpy(logRecord.source, thread->ThreadName.c_str()); logRecord.time = getValidTime(RTCQuality::RTCQualityDevice, true); - uint8_t *buffer = new uint8_t[meshtastic_LogRecord_size]; - size_t size = pb_encode_to_bytes(buffer, meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord); + auto buffer = std::unique_ptr(new uint8_t[meshtastic_LogRecord_size]); + size_t size = pb_encode_to_bytes(buffer.get(), meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord); #ifdef ARCH_ESP32 - nimbleBluetooth->sendLog(buffer, size); + nimbleBluetooth->sendLog(buffer.get(), size); #elif defined(ARCH_NRF52) - nrf52Bluetooth->sendLog(buffer, size); + nrf52Bluetooth->sendLog(buffer.get(), size); #endif - delete[] message; - delete[] buffer; } } #else @@ -292,8 +279,8 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) // append \n to format size_t len = strlen(format); - char *newFormat = new char[len + 2]; - strcpy(newFormat, format); + auto newFormat = std::unique_ptr(new char[len + 2]); + strcpy(newFormat.get(), format); newFormat[len] = '\n'; newFormat[len + 1] = '\0'; @@ -310,23 +297,18 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) va_end(arg); } if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { - delete[] newFormat; return; } } if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { - delete[] newFormat; return; } else if (portduino_config.logoutputlevel < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) { - delete[] newFormat; return; } else if (portduino_config.logoutputlevel < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) { - delete[] newFormat; return; } #endif if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { - delete[] newFormat; return; } @@ -338,11 +320,19 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) #endif va_list arg; + va_list arg_copy; + va_start(arg, format); - log_to_serial(logLevel, newFormat, arg); - log_to_syslog(logLevel, newFormat, arg); - log_to_ble(logLevel, newFormat, arg); + va_copy(arg_copy, arg); + log_to_serial(logLevel, newFormat.get(), arg_copy); + va_end(arg_copy); + + va_copy(arg_copy, arg); + log_to_syslog(logLevel, newFormat.get(), arg_copy); + va_end(arg_copy); + + log_to_ble(logLevel, newFormat.get(), arg); va_end(arg); #ifdef HAS_FREE_RTOS @@ -352,7 +342,6 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) #endif } - delete[] newFormat; return; } From aed2c7ca9b4adb10853edad0d5310537adf028d4 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 14 Feb 2026 19:56:53 +0100 Subject: [PATCH 148/223] replace delete in EInkDynamicDisplay.{cpp,h} with std::unique_ptr (#9643) Is part of the unique_ptr modernization effort. --- src/graphics/EInkDynamicDisplay.cpp | 6 +++--- src/graphics/EInkDynamicDisplay.h | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 8e4adf87e..892a4a885 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -10,7 +10,7 @@ EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDI { // If tracking ghost pixels, grab memory #ifdef EINK_LIMIT_GHOSTING_PX - dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros + dirtyPixels = std::unique_ptr(new uint8_t[EInkDisplay::displayBufferSize]()); // Init with zeros #endif } @@ -19,7 +19,7 @@ EInkDynamicDisplay::~EInkDynamicDisplay() { // If we were tracking ghost pixels, free the memory #ifdef EINK_LIMIT_GHOSTING_PX - delete[] dirtyPixels; + dirtyPixels = nullptr; #endif } @@ -454,7 +454,7 @@ void EInkDynamicDisplay::checkExcessiveGhosting() void EInkDynamicDisplay::resetGhostPixelTracking() { // Copy the current frame into dirtyPixels[] from the display buffer - memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize); + memcpy(dirtyPixels.get(), EInkDisplay::buffer, EInkDisplay::displayBufferSize); } #endif // EINK_LIMIT_GHOSTING_PX diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index d5e29e3f0..b03061873 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -1,6 +1,7 @@ #pragma once #include "configuration.h" +#include #if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) @@ -116,11 +117,11 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo // Optional - track ghosting, pixel by pixel // May 2024: no longer used by any display. Kept for possible future use. #ifdef EINK_LIMIT_GHOSTING_PX - void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh - void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit - void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display. - uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) - uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use + void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh + void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit + void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display. + std::unique_ptr dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) + uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use #endif // Conditional - async full refresh - only with modified meshtastic/GxEPD2 From 184e4ddd8302da38320085e9146ef6c50d08b3e4 Mon Sep 17 00:00:00 2001 From: Eric Barch Date: Sat, 14 Feb 2026 15:30:16 -0500 Subject: [PATCH 149/223] Undefine LED_BUILTIN for Heltec v2 variant (#9647) * Undefine LED_BUILTIN for Heltec v2 variant * Undefine LED_BUILTIN for Heltec v2.1 variant --------- Co-authored-by: Jorropo --- variants/esp32/heltec_v2.1/platformio.ini | 1 + variants/esp32/heltec_v2/platformio.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/variants/esp32/heltec_v2.1/platformio.ini b/variants/esp32/heltec_v2.1/platformio.ini index 1f7caa16f..9fcb2388a 100644 --- a/variants/esp32/heltec_v2.1/platformio.ini +++ b/variants/esp32/heltec_v2.1/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D HELTEC_V2_1 -I variants/esp32/heltec_v2.1 + -ULED_BUILTIN diff --git a/variants/esp32/heltec_v2/platformio.ini b/variants/esp32/heltec_v2/platformio.ini index 5f15fb321..fc9e05115 100644 --- a/variants/esp32/heltec_v2/platformio.ini +++ b/variants/esp32/heltec_v2/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D HELTEC_V2_0 -I variants/esp32/heltec_v2 + -ULED_BUILTIN From ff485d5ff89bd289b3485466e0dc0c6788318975 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 14 Feb 2026 21:39:26 +0100 Subject: [PATCH 150/223] replace delete in RadioInterface.cpp with std::unique_ptr (#9645) Is part of the unique_ptr modernization effort. --- src/main.cpp | 17 +++--- src/mesh/RadioInterface.cpp | 110 +++++++++++++++++------------------- src/mesh/RadioInterface.h | 3 +- src/mesh/Router.h | 5 +- 4 files changed, 64 insertions(+), 71 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 45685f6cd..d5e652a95 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -248,7 +248,6 @@ uint32_t timeLastPowered = 0; static OSThread *powerFSMthread; OSThread *ambientLightingThread; -RadioInterface *rIf = NULL; #ifdef ARCH_PORTDUINO RadioLibHal *RadioLibHAL = NULL; #endif @@ -954,7 +953,7 @@ void setup() #endif #endif - initLoRa(); + auto rIf = initLoRa(); lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation) @@ -1003,12 +1002,12 @@ void setup() if (!rIf) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_NO_RADIO); else { - router->addInterface(rIf); - // Log bit rate to debug output LOG_DEBUG("LoRA bitrate = %f bytes / sec", (float(meshtastic_Constants_DATA_PAYLOAD_LEN) / (float(rIf->getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN)))) * 1000); + + router->addInterface(std::move(rIf)); } // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values @@ -1150,10 +1149,7 @@ void loop() } if (portduino_status.LoRa_in_error && rebootAtMsec == 0) { LOG_ERROR("LoRa in error detected, attempting to recover"); - if (rIf != nullptr) { - delete rIf; - rIf = nullptr; - } + router->addInterface(nullptr); if (portduino_config.lora_spi_dev == "ch341") { if (ch341Hal != nullptr) { delete ch341Hal; @@ -1169,8 +1165,9 @@ void loop() exit(EXIT_FAILURE); } } - if (initLoRa()) { - router->addInterface(rIf); + auto rIf = initLoRa(); + if (rIf) { + router->addInterface(std::move(rIf)); portduino_status.LoRa_in_error = false; } else { LOG_WARN("Reconfigure failed, rebooting"); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 9e1ea3f21..ab3e1d8a0 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -227,18 +227,14 @@ static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; // Global LoRa radio type LoRaRadioType radioType = NO_RADIO; -extern RadioInterface *rIf; extern RadioLibHal *RadioLibHAL; #if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32) extern SPIClass SPI1; #endif -bool initLoRa() +std::unique_ptr initLoRa() { - if (rIf != nullptr) { - delete rIf; - rIf = nullptr; - } + std::unique_ptr rIf = nullptr; #if ARCH_PORTDUINO SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); @@ -252,26 +248,26 @@ bool initLoRa() RADIOLIB_PIN_TYPE busy) { switch (portduino_config.lora_module) { case use_rf95: - return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new RF95Interface(hal, cs, irq, rst, busy)); case use_sx1262: - return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new SX1262Interface(hal, cs, irq, rst, busy)); case use_sx1268: - return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new SX1268Interface(hal, cs, irq, rst, busy)); case use_sx1280: - return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new SX1280Interface(hal, cs, irq, rst, busy)); case use_lr1110: - return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new LR1110Interface(hal, cs, irq, rst, busy)); case use_lr1120: - return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new LR1120Interface(hal, cs, irq, rst, busy)); case use_lr1121: - return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new LR1121Interface(hal, cs, irq, rst, busy)); case use_llcc68: - return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new LLCC68Interface(hal, cs, irq, rst, busy)); case use_simradio: - return (RadioInterface *)new SimRadio; + return std::unique_ptr(new SimRadio); default: assert(0); // shouldn't happen - return (RadioInterface *)nullptr; + return std::unique_ptr(nullptr); } }; @@ -292,8 +288,7 @@ bool initLoRa() if (!rIf->init()) { LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str()); - delete rIf; - rIf = NULL; + rIf = nullptr; exit(EXIT_FAILURE); } else { LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str()); @@ -308,11 +303,11 @@ bool initLoRa() // radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) #if defined(USE_STM32WLx) if (!rIf) { - rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + rIf = std::unique_ptr( + new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No STM32WL radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("STM32WL init success"); radioType = STM32WLx_RADIO; @@ -322,11 +317,10 @@ bool initLoRa() #if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); + rIf = std::unique_ptr(new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1)); if (!rIf->init()) { LOG_WARN("No RF95 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("RF95 init success"); radioType = RF95_RADIO; @@ -336,17 +330,17 @@ bool initLoRa() #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + auto sxIf = + std::unique_ptr(new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); #ifdef SX126X_DIO3_TCXO_VOLTAGE sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); #endif if (!sxIf->init()) { LOG_WARN("No SX1262 radio"); - delete sxIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1262 init success"); - rIf = sxIf; + rIf = std::move(sxIf); radioType = SX1262_RADIO; } } @@ -355,26 +349,26 @@ bool initLoRa() #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // try using the specified TCXO voltage - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + auto sxIf = + std::unique_ptr(new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); if (!sxIf->init()) { LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; + rIf = std::move(sxIf); radioType = SX1262_RADIO; } } if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead - rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + rIf = + std::unique_ptr(new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1262 init success, XTAL, Vref 0.0V"); radioType = SX1262_RADIO; @@ -386,25 +380,25 @@ bool initLoRa() #if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL) if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // try using the specified TCXO voltage - auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + auto sxIf = + std::unique_ptr(new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); if (!sxIf->init()) { LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; + rIf = std::move(sxIf); radioType = SX1268_RADIO; } } #endif if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + rIf = + std::unique_ptr(new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1268 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1268 init success"); radioType = SX1268_RADIO; @@ -414,11 +408,11 @@ bool initLoRa() #if defined(USE_LLCC68) if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + rIf = + std::unique_ptr(new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No LLCC68 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("LLCC68 init success"); radioType = LLCC68_RADIO; @@ -428,11 +422,11 @@ bool initLoRa() #if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); + rIf = std::unique_ptr( + new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1110 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("LR1110 init success"); radioType = LR1110_RADIO; @@ -442,11 +436,11 @@ bool initLoRa() #if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 if (!rIf) { - rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); + rIf = std::unique_ptr( + new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1120 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("LR1120 init success"); radioType = LR1120_RADIO; @@ -456,11 +450,11 @@ bool initLoRa() #if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 if (!rIf) { - rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); + rIf = std::unique_ptr( + new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1121 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("LR1121 init success"); radioType = LR1121_RADIO; @@ -470,11 +464,11 @@ bool initLoRa() #if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 if (!rIf) { - rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); + rIf = + std::unique_ptr(new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1280 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1280 init success"); radioType = SX1280_RADIO; @@ -496,7 +490,7 @@ bool initLoRa() rebootAtMsec = millis() + 5000; } } - return rIf != nullptr; + return rIf; } void initRegion() diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index cb092bc6d..1fe3dd7b0 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -6,6 +6,7 @@ #include "PointerQueue.h" #include "airtime.h" #include "error.h" +#include // Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file. typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig; @@ -279,7 +280,7 @@ class RadioInterface } }; -bool initLoRa(); +std::unique_ptr initLoRa(); /// Debug printing for packets void printPacket(const char *prefix, const meshtastic_MeshPacket *p); diff --git a/src/mesh/Router.h b/src/mesh/Router.h index dbe6f4f39..dbb5e5802 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -8,6 +8,7 @@ #include "PointerQueue.h" #include "RadioInterface.h" #include "concurrency/OSThread.h" +#include /** * A mesh aware router that supports multiple interfaces. @@ -20,7 +21,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory PointerQueue fromRadioQueue; protected: - RadioInterface *iface = NULL; + std::unique_ptr iface = nullptr; public: /** @@ -32,7 +33,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory /** * Currently we only allow one interface, that may change in the future */ - void addInterface(RadioInterface *_iface) { iface = _iface; } + void addInterface(std::unique_ptr _iface) { iface = std::move(_iface); } /** * do idle processing From 167bcf2863d4278cd13d48f94564a240e78066dc Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 14 Feb 2026 22:12:31 +0100 Subject: [PATCH 151/223] fix typo in PIN_GPS_SWITCH (#9648) Wasn't caught by CI. --- src/gps/GPS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 58a512da1..2beaeb127 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -127,7 +127,7 @@ static int32_t gpsSwitch() return 1000; } -static str::unique_ptr gpsPeriodic; +static std::unique_ptr gpsPeriodic; #endif static void UBXChecksum(uint8_t *message, size_t length) From b0bd3df226f3e250b481e5a3c3e019e2722320cc Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sat, 14 Feb 2026 22:44:36 +0100 Subject: [PATCH 152/223] replace delete in CryptoEngine.{cpp,h} with std::unique_ptr (#9649) Is part of the unique_ptr modernization effort. --- src/mesh/CryptoEngine.cpp | 11 +++++------ src/mesh/CryptoEngine.h | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 0f4d64113..72216a63c 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -1,6 +1,7 @@ #include "CryptoEngine.h" // #include "NodeDB.h" #include "architecture.h" +#include #if !(MESHTASTIC_EXCLUDE_PKI) #include "NodeDB.h" @@ -169,10 +170,9 @@ void CryptoEngine::hash(uint8_t *bytes, size_t numBytes) void CryptoEngine::aesSetKey(const uint8_t *key_bytes, size_t key_len) { - delete aes; aes = nullptr; if (key_len != 0) { - aes = new AESSmall256(); + aes = std::unique_ptr(new AESSmall256()); aes->setKey(key_bytes, key_len); } } @@ -231,12 +231,11 @@ void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes // Generic implementation of AES-CTR encryption. void CryptoEngine::encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) { - delete ctr; - ctr = nullptr; + std::unique_ptr ctr; if (_key.length == 16) - ctr = new CTR(); + ctr = std::unique_ptr(new CTR()); else - ctr = new CTR(); + ctr = std::unique_ptr(new CTR()); ctr->setKey(_key.bytes, _key.length); static uint8_t scratch[MAX_BLOCKSIZE]; memcpy(scratch, bytes, numBytes); diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 7689006ab..19d572355 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -5,6 +5,7 @@ #include "configuration.h" #include "mesh-pb-constants.h" #include +#include extern concurrency::Lock *cryptLock; @@ -48,7 +49,7 @@ class CryptoEngine virtual void aesSetKey(const uint8_t *key, size_t key_len); virtual void aesEncrypt(uint8_t *in, uint8_t *out); - AESSmall256 *aes = NULL; + std::unique_ptr aes = nullptr; #endif @@ -77,7 +78,6 @@ class CryptoEngine /** Our per packet nonce */ uint8_t nonce[16] = {0}; CryptoKey key = {}; - CTRCommon *ctr = NULL; #if !(MESHTASTIC_EXCLUDE_PKI) uint8_t shared_key[32] = {0}; uint8_t private_key[32] = {0}; From dbef1de2861c49fe6f5fe93ced6674f12e227d0f Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sun, 15 Feb 2026 00:32:19 +0100 Subject: [PATCH 153/223] workaround NCP5623 and LP5562 I2C builds (#9652) Theses two appear to be buggy on r1-neo and nomadstar meteor pro, they rely on Wire.h being included previously to their import. Idk why other platforms using the same smart LEDs are working while theses ones don't. This should make CI green on the dev branch. --- src/AmbientLightingThread.h | 2 ++ src/graphics/NomadStarLED.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index bd9557c21..d52b10a53 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -7,6 +7,8 @@ #include "sleep.h" #ifdef HAS_NCP5623 +#include + #include #endif diff --git a/src/graphics/NomadStarLED.h b/src/graphics/NomadStarLED.h index 0633a577e..6633db0c8 100644 --- a/src/graphics/NomadStarLED.h +++ b/src/graphics/NomadStarLED.h @@ -1,4 +1,6 @@ #ifdef HAS_LP5562 +#include + #include extern LP5562 rgbw; From 545826d319553f3bb570cc37a33688a1e172ec6b Mon Sep 17 00:00:00 2001 From: Jorropo Date: Sun, 15 Feb 2026 13:49:16 +0100 Subject: [PATCH 154/223] replace delete in AudioThread.h with std::unique_ptr (#9651) Is part of the unique_ptr modernization effort. --- src/AudioThread.h | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/AudioThread.h b/src/AudioThread.h index 23552c421..e1ba422bc 100644 --- a/src/AudioThread.h +++ b/src/AudioThread.h @@ -4,6 +4,7 @@ #include "configuration.h" #include "main.h" #include "sleep.h" +#include #ifdef HAS_I2S #include @@ -29,9 +30,9 @@ class AudioThread : public concurrency::OSThread io.digitalWrite(EXPANDS_AMP_EN, HIGH); #endif setCPUFast(true); - rtttlFile = new AudioFileSourcePROGMEM(data, len); - i2sRtttl = new AudioGeneratorRTTTL(); - i2sRtttl->begin(rtttlFile, audioOut); + rtttlFile = std::unique_ptr(new AudioFileSourcePROGMEM(data, len)); + i2sRtttl = std::unique_ptr(new AudioGeneratorRTTTL()); + i2sRtttl->begin(rtttlFile.get(), audioOut.get()); } // Also handles actually playing the RTTTL, needs to be called in loop @@ -47,12 +48,10 @@ class AudioThread : public concurrency::OSThread { if (i2sRtttl != nullptr) { i2sRtttl->stop(); - delete i2sRtttl; i2sRtttl = nullptr; } if (rtttlFile != nullptr) { - delete rtttlFile; rtttlFile = nullptr; } @@ -66,16 +65,14 @@ class AudioThread : public concurrency::OSThread { if (i2sRtttl != nullptr) { i2sRtttl->stop(); - delete i2sRtttl; i2sRtttl = nullptr; } #ifdef T_LORA_PAGER io.digitalWrite(EXPANDS_AMP_EN, HIGH); #endif - ESP8266SAM *sam = new ESP8266SAM; - sam->Say(audioOut, text); - delete sam; + auto sam = std::unique_ptr(new ESP8266SAM); + sam->Say(audioOut.get(), text); setCPUFast(false); #ifdef T_LORA_PAGER io.digitalWrite(EXPANDS_AMP_EN, LOW); @@ -96,15 +93,15 @@ class AudioThread : public concurrency::OSThread private: void initOutput() { - audioOut = new AudioOutputI2S(1, AudioOutputI2S::EXTERNAL_I2S); + audioOut = std::unique_ptr(new AudioOutputI2S(1, AudioOutputI2S::EXTERNAL_I2S)); audioOut->SetPinout(DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_MCLK); audioOut->SetGain(0.2); }; - AudioGeneratorRTTTL *i2sRtttl = nullptr; - AudioOutputI2S *audioOut = nullptr; + std::unique_ptr i2sRtttl = nullptr; + std::unique_ptr audioOut = nullptr; - AudioFileSourcePROGMEM *rtttlFile = nullptr; + std::unique_ptr rtttlFile = nullptr; }; #endif From 778823e623c3b8c276beda609ed698912409124f Mon Sep 17 00:00:00 2001 From: Clive Blackledge Date: Mon, 16 Feb 2026 01:16:05 -0800 Subject: [PATCH 155/223] Add USB_MODE=1 for Station G2 (#9660) --- boards/station-g2.json | 2 +- variants/esp32s3/station-g2/platformio.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/boards/station-g2.json b/boards/station-g2.json index 871f067aa..f7ce50779 100755 --- a/boards/station-g2.json +++ b/boards/station-g2.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=0" ], diff --git a/variants/esp32s3/station-g2/platformio.ini b/variants/esp32s3/station-g2/platformio.ini index 091b35f00..4efb21a00 100755 --- a/variants/esp32s3/station-g2/platformio.ini +++ b/variants/esp32s3/station-g2/platformio.ini @@ -21,11 +21,11 @@ upload_protocol = esptool upload_speed = 921600 build_unflags = ${esp32s3_base.build_unflags} - -DARDUINO_USB_MODE=1 + -DARDUINO_USB_MODE=0 build_flags = ${esp32s3_base.build_flags} -D STATION_G2 -I variants/esp32s3/station-g2 -DBOARD_HAS_PSRAM -DSTATION_G2 - -DARDUINO_USB_MODE=0 + -DARDUINO_USB_MODE=1 From 32db70037d2168bdac5ba2afa58427be2746a60d Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:35:27 -0500 Subject: [PATCH 156/223] InkHUD: Favorite Map Applet (#9654) --- .../User/FavoritesMap/FavoritesMapApplet.cpp | 111 ++++++++++++++++++ .../User/FavoritesMap/FavoritesMapApplet.h | 44 +++++++ .../heltec_vision_master_e213/nicheGraphics.h | 4 +- .../heltec_vision_master_e290/nicheGraphics.h | 4 +- .../heltec_wireless_paper/nicheGraphics.h | 4 +- .../esp32s3/tlora_t3s3_epaper/nicheGraphics.h | 4 +- .../ELECROW-ThinkNode-M1/nicheGraphics.h | 18 +-- .../nrf52_promicro_diy_tcxo/nicheGraphics.h | 6 +- .../nicheGraphics.h | 6 +- .../heltec_mesh_pocket/nicheGraphics.h | 4 +- .../heltec_mesh_solar/nicheGraphics.h | 4 +- .../seeed_wio_tracker_L1_eink/nicheGraphics.h | 3 + variants/nrf52840/t-echo-plus/nicheGraphics.h | 2 + variants/nrf52840/t-echo/nicheGraphics.h | 4 +- 14 files changed, 199 insertions(+), 19 deletions(-) create mode 100644 src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp create mode 100644 src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h diff --git a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp new file mode 100644 index 000000000..6963df54a --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp @@ -0,0 +1,111 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +#include "./FavoritesMapApplet.h" +#include "NodeDB.h" + +using namespace NicheGraphics; + +bool InkHUD::FavoritesMapApplet::shouldDrawNode(meshtastic_NodeInfoLite *node) +{ + // Keep our own node available as map anchor/center; all others must be favorited. + return node && (node->num == nodeDB->getNodeNum() || node->is_favorite); +} + +void InkHUD::FavoritesMapApplet::onRender(bool full) +{ + // Custom empty state text for favorites-only map. + if (!enoughMarkers()) { + printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Favorite node position", CENTER, MIDDLE); + printAt(X(0.5), Y(0.5) + (getFont().lineHeight() / 2), "will appear here", CENTER, MIDDLE); + return; + } + + // Draw the usual map applet first. + MapApplet::onRender(full); + + // Draw our latest "node of interest" as a special marker. + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(lastFrom); + if (node && node->is_favorite && nodeDB->hasValidPosition(node) && enoughMarkers()) + drawLabeledMarker(node); +} + +// Determine if we need to redraw the map, when we receive a new position packet. +ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshPacket &mp) +{ + // If applet is not active, we shouldn't be handling any data. + if (!isActive()) + return ProcessMessage::CONTINUE; + + // Try decode a position from the packet. + bool hasPosition = false; + float lat; + float lng; + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == meshtastic_PortNum_POSITION_APP) { + meshtastic_Position position = meshtastic_Position_init_default; + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Position_msg, &position)) { + if (position.has_latitude_i && position.has_longitude_i // Actually has position + && (position.latitude_i != 0 || position.longitude_i != 0)) // Position isn't "null island" + { + hasPosition = true; + lat = position.latitude_i * 1e-7; // Convert from Meshtastic's internal int32_t format + lng = position.longitude_i * 1e-7; + } + } + } + + // Skip if we didn't get a valid position. + if (!hasPosition) + return ProcessMessage::CONTINUE; + + const int8_t hopsAway = getHopsAway(mp); + const bool hasHopsAway = hopsAway >= 0; + + // Determine if the position packet would change anything on-screen. + bool somethingChanged = false; + + // If our own position. + if (isFromUs(&mp)) { + // Ignore tiny local movement to reduce update spam. + if (GeoCoord::latLongToMeter(ourLastLat, ourLastLng, lat, lng) > 50) { + somethingChanged = true; + ourLastLat = lat; + ourLastLng = lng; + } + } else { + // For non-local packets, this applet only reacts to favorited nodes. + meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); + if (!sender || !sender->is_favorite) + return ProcessMessage::CONTINUE; + + // Check if this position is from someone different than our previous position packet. + if (mp.from != lastFrom) { + somethingChanged = true; + lastFrom = mp.from; + lastLat = lat; + lastLng = lng; + lastHopsAway = hopsAway; + } + + // Same sender: check if position changed. + else if (GeoCoord::latLongToMeter(lastLat, lastLng, lat, lng) > 10) { + somethingChanged = true; + lastLat = lat; + lastLng = lng; + } + + // Same sender, same position: check if hops changed. + else if (hasHopsAway && (hopsAway != lastHopsAway)) { + somethingChanged = true; + lastHopsAway = hopsAway; + } + } + + if (somethingChanged) { + requestAutoshow(); + requestUpdate(); + } + + return ProcessMessage::CONTINUE; +} + +#endif diff --git a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h new file mode 100644 index 000000000..da5fb0dc3 --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h @@ -0,0 +1,44 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +/* + +Plots position of favorited nodes from DB, with North facing up. +Scaled to fit the most distant node. +Size of marker represents hops away. +The favorite node which most recently sent a position will be labeled. + +*/ + +#pragma once + +#include "configuration.h" + +#include "graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h" + +#include "SinglePortModule.h" + +namespace NicheGraphics::InkHUD +{ + +class FavoritesMapApplet : public MapApplet, public SinglePortModule +{ + public: + FavoritesMapApplet() : SinglePortModule("FavoritesMapApplet", meshtastic_PortNum_POSITION_APP) {} + void onRender(bool full) override; + + protected: + bool shouldDrawNode(meshtastic_NodeInfoLite *node) override; + ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + NodeNum lastFrom = 0; // Sender of most recent favorited (non-local) position packet + float lastLat = 0.0; + float lastLng = 0.0; + float lastHopsAway = 0; + + float ourLastLat = 0.0; // Info about most recent local position + float ourLastLng = 0.0; +}; + +} // namespace NicheGraphics::InkHUD + +#endif diff --git a/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h b/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h index 1b1291424..fb0744bc3 100644 --- a/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h +++ b/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -87,6 +88,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 @@ -114,4 +116,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h b/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h index 61b08c740..a90500b15 100644 --- a/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h +++ b/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h @@ -24,6 +24,7 @@ Different NicheGraphics UIs and different hardware variants will each have their // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -84,6 +85,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 @@ -111,4 +113,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h b/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h index 445b57714..9e84a541e 100644 --- a/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h +++ b/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -87,6 +88,7 @@ void setupNicheGraphics() inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // - // Start running InkHUD inkhud->begin(); @@ -107,4 +109,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h b/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h index 8f5e63653..73cc2e235 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h +++ b/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -67,6 +68,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 @@ -86,4 +88,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h b/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h index f64de9d07..242e5ae49 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -71,13 +72,14 @@ void setupNicheGraphics() // Pick applets // Note: order of applets determines priority of "auto-show" feature - inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown - inkhud->addApplet("DMs", new InkHUD::DMApplet); // - - inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 + inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown + inkhud->addApplet("DMs", new InkHUD::DMApplet); // - + inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - + inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - + inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - + inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // - // Start running InkHUD inkhud->begin(); @@ -115,4 +117,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h index 8f30a244f..0a01b613e 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -72,7 +73,8 @@ void setupNicheGraphics() inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false, 3); // Default on tile 3 inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 2); // Default on tile 2 inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1 + inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false, 0); // Default on tile 0 inkhud->addApplet("Heard", new InkHUD::HeardApplet, true); // Background @@ -92,4 +94,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h index b6be70ff4..ad17e7457 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -73,7 +74,8 @@ void setupNicheGraphics() inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false, 3); // Default on tile 3 inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 2); // Default on tile 2 inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1 + inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false, 0); // Default on tile 0 inkhud->addApplet("Heard", new InkHUD::HeardApplet, true); // Background @@ -93,4 +95,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h b/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h index 10f628d56..187022ea7 100644 --- a/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h +++ b/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -67,6 +68,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 @@ -87,4 +89,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h index 125f50590..0f4131916 100644 --- a/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h +++ b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -67,6 +68,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 @@ -87,4 +89,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h index 98aeb8700..9c9c788c7 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -63,6 +64,7 @@ void setupNicheGraphics() inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side + inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Pick applets // Note: order of applets determines priority of "auto-show" feature @@ -74,6 +76,7 @@ void setupNicheGraphics() inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // - // Start running InkHUD inkhud->begin(); diff --git a/variants/nrf52840/t-echo-plus/nicheGraphics.h b/variants/nrf52840/t-echo-plus/nicheGraphics.h index 483e16ea4..73067d7a7 100644 --- a/variants/nrf52840/t-echo-plus/nicheGraphics.h +++ b/variants/nrf52840/t-echo-plus/nicheGraphics.h @@ -8,6 +8,7 @@ #include "graphics/niche/Drivers/EInk/GDEY0154D67.h" #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -43,6 +44,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); diff --git a/variants/nrf52840/t-echo/nicheGraphics.h b/variants/nrf52840/t-echo/nicheGraphics.h index c89d816b9..c0b24dea7 100644 --- a/variants/nrf52840/t-echo/nicheGraphics.h +++ b/variants/nrf52840/t-echo/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -79,6 +80,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 @@ -123,4 +125,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif From 56fd9c78132b24d20b9e234cfb87cda24374c00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 16 Feb 2026 12:28:07 +0100 Subject: [PATCH 157/223] fix a lot of low level cppcheck warnings (#9623) * simplify the observer pattern, since all the called functions are const getters. * use arduino macro over std: for numerical values and refactor local variables in drawScrollbar() * oh, so Cppcheck actually complained about const pointers not being const. * slowly getting out of ifdef hell * fix inkHUD warnings as well * last 2 check warnings * git checks should fail on low defects from now on --- .github/workflows/main_matrix.yml | 2 +- src/Power.cpp | 8 ++-- src/gps/RTC.cpp | 2 +- src/gps/RTC.h | 2 +- src/graphics/draw/MenuHandler.cpp | 23 +++++------ src/graphics/draw/NodeListRenderer.cpp | 23 +++++------ src/graphics/draw/UIRenderer.cpp | 4 +- src/graphics/draw/UIRenderer.h | 2 +- .../Drivers/Backlight/LatchingBacklight.cpp | 8 ++-- .../Drivers/Backlight/LatchingBacklight.h | 2 +- src/graphics/niche/InkHUD/Applet.cpp | 30 +++++++------- src/graphics/niche/InkHUD/Applet.h | 17 ++++---- src/graphics/niche/InkHUD/AppletFont.cpp | 14 +++---- src/graphics/niche/InkHUD/AppletFont.h | 11 ++--- .../Applets/Bases/NodeList/NodeListApplet.cpp | 14 +++---- .../System/BatteryIcon/BatteryIconApplet.cpp | 4 +- .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 8 ++-- .../InkHUD/Applets/System/Menu/MenuApplet.h | 2 +- .../Notification/NotificationApplet.cpp | 10 ++--- .../Applets/System/Pairing/PairingApplet.cpp | 6 +-- .../InkHUD/Applets/User/Heard/HeardApplet.cpp | 7 ++-- .../ThreadedMessage/ThreadedMessageApplet.cpp | 2 +- src/graphics/niche/InkHUD/MessageStore.cpp | 28 +++++++------ src/graphics/niche/InkHUD/MessageStore.h | 2 +- src/graphics/niche/InkHUD/Renderer.cpp | 26 ++++++------ src/graphics/niche/InkHUD/WindowManager.cpp | 2 +- .../niche/Utils/CannedMessageStore.cpp | 2 +- src/input/TCA8418Keyboard.cpp | 3 +- src/main.cpp | 15 +++---- src/mesh/NodeDB.cpp | 12 +++--- src/mesh/PacketHistory.cpp | 4 +- src/mesh/PacketHistory.h | 4 +- src/mesh/RadioInterface.cpp | 40 +++++++++---------- src/mesh/StreamAPI.cpp | 4 +- src/mesh/StreamAPI.h | 4 +- src/mesh/aes-ccm.cpp | 2 +- src/mesh/api/PacketAPI.h | 2 +- src/mesh/http/ContentHandler.cpp | 2 +- src/modules/CannedMessageModule.cpp | 11 ++--- src/modules/ExternalNotificationModule.cpp | 2 +- src/modules/StatusLEDModule.cpp | 23 +++++------ src/modules/StoreForwardModule.cpp | 4 +- .../Telemetry/Sensor/AddI2CSensorTemplate.h | 2 +- .../Telemetry/Sensor/PMSA003ISensor.cpp | 2 +- src/modules/Telemetry/Sensor/SCD4XSensor.cpp | 4 +- src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 2 +- src/modules/Telemetry/Sensor/SEN5XSensor.h | 2 +- src/modules/TraceRouteModule.cpp | 2 +- src/modules/TraceRouteModule.h | 2 +- src/platform/esp32/MeshtasticOTA.cpp | 2 +- src/platform/esp32/MeshtasticOTA.h | 2 +- src/platform/nrf52/AsyncUDP.h | 2 +- src/platform/portduino/PortduinoGlue.cpp | 12 +++--- src/platform/portduino/PortduinoGlue.h | 12 +++--- src/platform/portduino/USBHal.h | 2 +- .../esp32c6/m5stack_unitc6l/platformio.ini | 2 +- 56 files changed, 217 insertions(+), 226 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 6b48e8128..49379fbd1 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -95,7 +95,7 @@ jobs: with: pio_platform: ${{ matrix.check.platform }} pio_env: ${{ matrix.check.board }} - pio_target: check + pio_target: check --fail-on-defect=low build: needs: [setup, version] diff --git a/src/Power.cpp b/src/Power.cpp index 6ee658f2b..ea4fcf42a 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -704,11 +704,11 @@ bool Power::setup() found = true; } else if (analogInit()) { found = true; - } - + } else { #ifdef NRF_APM - found = true; + found = true; #endif + } #ifdef EXT_PWR_DETECT attachInterrupt( EXT_PWR_DETECT, @@ -846,8 +846,10 @@ void Power::readPowerStatus() if (batteryLevel) { hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse; +#ifndef NRF_APM usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse; isChargingNow = batteryLevel->isCharging() ? OptTrue : OptFalse; +#endif if (hasBattery) { batteryVoltageMv = batteryLevel->getBattVoltage(); // If the AXP192 returns a valid battery percentage, use it diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 5e31de950..e67bef53e 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -312,7 +312,7 @@ const char *RtcName(RTCQuality quality) * @param t The time to potentially set the RTC to. * @return True if the RTC was set to the provided time, false otherwise. */ -RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) +RTCSetResult perhapsSetRTC(RTCQuality q, const struct tm &t) { /* Convert to unix time The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 diff --git a/src/gps/RTC.h b/src/gps/RTC.h index cf6db0239..16ecd8245 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -41,7 +41,7 @@ extern uint32_t lastSetFromPhoneNtpOrGps; /// If we haven't yet set our RTC this boot, set it from a GPS derived time RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); -RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t); +RTCSetResult perhapsSetRTC(RTCQuality q, const struct tm &t); /// Return a string name for the quality const char *RtcName(RTCQuality quality); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 195da09f9..f57c39512 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -539,7 +539,7 @@ void menuHandler::messageResponseMenu() // If viewing ALL chats, hide “Mute Chat” if (mode != graphics::MessageRenderer::ThreadMode::ALL && mode != graphics::MessageRenderer::ThreadMode::DIRECT) { const uint8_t chIndex = (threadChannel != 0) ? (uint8_t)threadChannel : channels.getPrimaryIndex(); - auto &chan = channels.getByIndex(chIndex); + const auto &chan = channels.getByIndex(chIndex); optionsArray[options] = chan.settings.module_settings.is_muted ? "Unmute Channel" : "Mute Channel"; optionsEnumArray[options++] = MuteChannel; @@ -831,7 +831,7 @@ void menuHandler::messageViewModeMenu() // Gather unique peers auto dms = messageStore.getDirectMessages(); std::vector uniquePeers; - for (auto &m : dms) { + for (const auto &m : dms) { uint32_t peer = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender; if (peer != nodeDB->getNodeNum() && std::find(uniquePeers.begin(), uniquePeers.end(), peer) == uniquePeers.end()) uniquePeers.push_back(peer); @@ -1397,7 +1397,7 @@ void menuHandler::manageNodeMenu() } if (selected == Favorite) { - auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum); + const auto *n = nodeDB->getMeshNode(menuHandler::pickedNodeNum); if (!n) { return; } @@ -2292,14 +2292,13 @@ void menuHandler::wifiToggleMenu() void menuHandler::screenOptionsMenu() { // Check if brightness is supported - bool hasSupportBrightness = false; -#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) - hasSupportBrightness = true; -#endif - #if defined(T_DECK) // TDeck Doesn't seem to support brightness at all, at least not reliably - hasSupportBrightness = false; + bool hasSupportBrightness = false; +#elif defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) + bool hasSupportBrightness = true; +#else + bool hasSupportBrightness = false; #endif enum optionsNumbers { Back, Brightness, ScreenColor, FrameToggles, DisplayUnits, MessageBubbles }; @@ -2444,7 +2443,7 @@ void menuHandler::frameTogglesMenu() nodelist_hopsignal, nodelist_distance, nodelist_bearings, - gps, + gps_position, lora, clock, show_favorites, @@ -2482,7 +2481,7 @@ void menuHandler::frameTogglesMenu() #endif optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position"; - optionsEnumArray[options++] = gps; + optionsEnumArray[options++] = gps_position; #endif optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa"; @@ -2545,7 +2544,7 @@ void menuHandler::frameTogglesMenu() screen->toggleFrameVisibility("nodelist_bearings"); menuHandler::menuQueue = menuHandler::FrameToggles; screen->runNow(); - } else if (selected == gps) { + } else if (selected == gps_position) { screen->toggleFrameVisibility("gps"); menuHandler::menuQueue = menuHandler::FrameToggles; screen->runNow(); diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 9d6780130..b36a5057c 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -171,7 +171,7 @@ unsigned long getModeCycleIntervalMs() int calculateMaxScroll(int totalEntries, int visibleRows) { - return std::max(0, (totalEntries - 1) / (visibleRows * 2)); + return max(0, (totalEntries - 1) / (visibleRows * 2)); } void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd) @@ -187,13 +187,12 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries, if (totalEntries <= visibleNodeRows * columns) return; - int scrollbarX = display->getWidth() - 2; int scrollbarHeight = display->getHeight() - scrollStartY - 10; - int thumbHeight = std::max(4, (scrollbarHeight * visibleNodeRows * columns) / totalEntries); - int perPage = visibleNodeRows * columns; - int maxScroll = std::max(0, (totalEntries - 1) / perPage); - int thumbY = scrollStartY + (scrollIndex * (scrollbarHeight - thumbHeight)) / std::max(1, maxScroll); + int thumbHeight = max(4, (scrollbarHeight * visibleNodeRows * columns) / totalEntries); + int thumbY = scrollStartY + (scrollIndex * (scrollbarHeight - thumbHeight)) / + max(1, max(0, (totalEntries - 1) / (visibleNodeRows * columns))); + int scrollbarX = display->getWidth() - 2; for (int i = 0; i < thumbHeight; i++) { display->setPixel(scrollbarX, thumbY + i); } @@ -556,13 +555,13 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t int maxScroll = 0; if (perPage > 0) { - maxScroll = std::max(0, (totalEntries - 1) / perPage); + maxScroll = max(0, (totalEntries - 1) / perPage); } if (scrollIndex > maxScroll) scrollIndex = maxScroll; int startIndex = scrollIndex * visibleNodeRows * totalColumns; - int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries); + int endIndex = min(startIndex + visibleNodeRows * totalColumns, totalEntries); int yOffset = 0; int col = 0; int lastNodeY = y; @@ -580,7 +579,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t if (extras) extras(display, node, xPos, yPos, columnWidth, heading, lat, lon); - lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL); + lastNodeY = max(lastNodeY, yPos + FONT_HEIGHT_SMALL); yOffset += rowYOffset; shownCount++; rowCount++; @@ -613,13 +612,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t if (millis() - popupTime < POPUP_DURATION_MS) { popupTotal = totalEntries; - int perPage = visibleNodeRows * totalColumns; - popupStart = startIndex + 1; - popupEnd = std::min(startIndex + perPage, totalEntries); + popupEnd = min(startIndex + perPage, totalEntries); popupPage = (scrollIndex + 1); - popupMaxPage = std::max(1, (totalEntries + perPage - 1) / perPage); + popupMaxPage = max(1, (totalEntries + perPage - 1) / perPage); char buf[32]; snprintf(buf, sizeof(buf), "%d-%d/%d Pg %d/%d", popupStart, popupEnd, popupTotal, popupPage, popupMaxPage); diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 922ca1028..25a70f16d 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -288,7 +288,8 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes // ********************** // * Favorite Node Info * // ********************** -void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y) +// cppcheck-suppress constParameterPointer; signature must match FrameCallback typedef from OLEDDisplayUi library +void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { if (favoritedNodes.empty()) return; @@ -1388,6 +1389,7 @@ static int8_t lastFrameIndex = -1; static uint32_t lastFrameChangeTime = 0; constexpr uint32_t ICON_DISPLAY_DURATION_MS = 2000; +// cppcheck-suppress constParameterPointer; signature must match OverlayCallback typedef from OLEDDisplayUi library void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state) { int currentFrame = state->currentFrame; diff --git a/src/graphics/draw/UIRenderer.h b/src/graphics/draw/UIRenderer.h index 6e37b68f2..8f0d07881 100644 --- a/src/graphics/draw/UIRenderer.h +++ b/src/graphics/draw/UIRenderer.h @@ -49,7 +49,7 @@ class UIRenderer // Navigation bar overlay static void drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state); - static void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y); + static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); diff --git a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp b/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp index 6d9b709b1..ad92e28ea 100644 --- a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp +++ b/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp @@ -42,7 +42,7 @@ int LatchingBacklight::beforeDeepSleep(void *unused) { // Contingency only // - pin wasn't set - if (pin != (uint8_t)-1) { + if (pin != static_cast(-1)) { off(); pinMode(pin, INPUT); // High impedance - unnecessary? } else @@ -55,7 +55,7 @@ int LatchingBacklight::beforeDeepSleep(void *unused) // The effect on the backlight is the same; peek and latch are separated to simplify short vs long press button handling void LatchingBacklight::peek() { - assert(pin != (uint8_t)-1); + assert(pin != static_cast(-1)); digitalWrite(pin, logicActive); // On on = true; latched = false; @@ -67,7 +67,7 @@ void LatchingBacklight::peek() // The effect on the backlight is the same; peek and latch are separated to simplify short vs long press button handling void LatchingBacklight::latch() { - assert(pin != (uint8_t)-1); + assert(pin != static_cast(-1)); // Blink if moving from peek to latch // Indicates to user that the transition has taken place @@ -89,7 +89,7 @@ void LatchingBacklight::latch() // Suitable for ending both peek and latch void LatchingBacklight::off() { - assert(pin != (uint8_t)-1); + assert(pin != static_cast(-1)); digitalWrite(pin, !logicActive); // Off on = false; latched = false; diff --git a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h b/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h index 0097cae4c..87862ea1b 100644 --- a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h +++ b/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h @@ -40,7 +40,7 @@ class LatchingBacklight CallbackObserver deepSleepObserver = CallbackObserver(this, &LatchingBacklight::beforeDeepSleep); - uint8_t pin = (uint8_t)-1; + uint8_t pin = static_cast(-1); bool logicActive = HIGH; // Is light active HIGH or active LOW bool on = false; // Is light on (either peek or latched) diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index 0616d03cd..1468bb23d 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -34,7 +34,7 @@ void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color) { // Only render pixels if they fall within user's cropped region if (x >= cropLeft && x < (cropLeft + cropWidth) && y >= cropTop && y < (cropTop + cropHeight)) - assignedTile->handleAppletPixel(x, y, (Color)color); + assignedTile->handleAppletPixel(x, y, static_cast(color)); } // Link our applet to a tile @@ -312,7 +312,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, const char *text, HorizontalA } // Print text, specifying the position of any edge / corner of the textbox -void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha, VerticalAlignment va) +void InkHUD::Applet::printAt(int16_t x, int16_t y, const std::string &text, HorizontalAlignment ha, VerticalAlignment va) { printAt(x, y, text.c_str(), ha, va); } @@ -334,7 +334,7 @@ InkHUD::AppletFont InkHUD::Applet::getFont() // Parse any text which might have "special characters" // Re-encodes UTF-8 characters to match our 8-bit encoded fonts -std::string InkHUD::Applet::parse(std::string text) +std::string InkHUD::Applet::parse(const std::string &text) { return getFont().decodeUTF8(text); } @@ -361,10 +361,10 @@ std::string InkHUD::Applet::parseShortName(meshtastic_NodeInfoLite *node) } // Determine if all characters of a string are printable using the current font -bool InkHUD::Applet::isPrintable(std::string text) +bool InkHUD::Applet::isPrintable(const std::string &text) { // Scan for SUB (0x1A), which is the value assigned by AppletFont::applyEncoding if a unicode character is not handled - for (char &c : text) { + for (const char &c : text) { if (c == '\x1A') return false; } @@ -387,7 +387,7 @@ uint16_t InkHUD::Applet::getTextWidth(const char *text) // Gets rendered width of a string // Wrapper for getTextBounds -uint16_t InkHUD::Applet::getTextWidth(std::string text) +uint16_t InkHUD::Applet::getTextWidth(const std::string &text) { return getTextWidth(text.c_str()); } @@ -435,7 +435,7 @@ std::string InkHUD::Applet::hexifyNodeNum(NodeNum num) // Print text, with word wrapping // Avoids splitting words in half, instead moving the entire word to a new line wherever possible -void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std::string text) +void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, const std::string &text) { // Place the AdafruitGFX cursor to suit our "top" coord setCursor(left, top + getFont().heightAboveCursor()); @@ -492,15 +492,15 @@ void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std // Todo: rewrite making use of AdafruitGFX native text wrapping char cstr[] = {0, 0}; - int16_t l, t; - uint16_t w, h; + int16_t bx, by; + uint16_t bw, bh; for (uint16_t c = 0; c < word.length(); c++) { // Shove next char into a c string cstr[0] = word[c]; - getTextBounds(cstr, getCursorX(), getCursorY(), &l, &t, &w, &h); + getTextBounds(cstr, getCursorX(), getCursorY(), &bx, &by, &bw, &bh); // Manual newline, if next character will spill beyond screen edge - if ((l + w) > left + width) + if ((bx + bw) > left + width) setCursor(left, getCursorY() + getFont().lineHeight()); // Print next character @@ -519,7 +519,7 @@ void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std // Simulate running printWrapped, to determine how tall the block of text will be. // This is a wasteful way of handling things. Maybe some way to optimize in future? -uint32_t InkHUD::Applet::getWrappedTextHeight(int16_t left, uint16_t width, std::string text) +uint32_t InkHUD::Applet::getWrappedTextHeight(int16_t left, uint16_t width, const std::string &text) { // Cache the current crop region int16_t cL = cropLeft; @@ -649,7 +649,7 @@ uint16_t InkHUD::Applet::getActiveNodeCount() // For each node in db for (uint16_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { - meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); + const meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); // Check if heard recently, and not our own node if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum()) @@ -702,7 +702,7 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters) } // Print text with a "faux bold" effect, by drawing it multiple times, offsetting slightly -void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY) +void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, const std::string &text, uint8_t thicknessX, uint8_t thicknessY) { // How many times to draw along x axis int16_t xStart; @@ -770,7 +770,7 @@ bool InkHUD::Applet::approveNotification(NicheGraphics::InkHUD::Notification &n) │ │ └───────────────────────────────┘ */ -void InkHUD::Applet::drawHeader(std::string text) +void InkHUD::Applet::drawHeader(const std::string &text) { // Y position for divider // - between header text and messages diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index 69d35a234..2a349052d 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -125,16 +125,17 @@ class Applet : public GFX void setFont(AppletFont f); AppletFont getFont(); - uint16_t getTextWidth(std::string text); + uint16_t getTextWidth(const std::string &text); uint16_t getTextWidth(const char *text); - uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text); // Result of printWrapped + uint32_t getWrappedTextHeight(int16_t left, uint16_t width, const std::string &text); // Result of printWrapped void printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP); - void printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP); - void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY); // Faux bold - void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text); // Per-word line wrapping + void printAt(int16_t x, int16_t y, const std::string &text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP); + void printThick(int16_t xCenter, int16_t yCenter, const std::string &text, uint8_t thicknessX, + uint8_t thicknessY); // Faux bold + void printWrapped(int16_t left, int16_t top, uint16_t width, const std::string &text); // Per-word line wrapping void hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines - void drawHeader(std::string text); // Draw the standard applet header + void drawHeader(const std::string &text); // Draw the standard applet header // Meshtastic Logo @@ -150,9 +151,9 @@ class Applet : public GFX std::string getTimeString(); // Current time, human readable uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric - std::string parse(std::string text); // Handle text which might contain special chars + std::string parse(const std::string &text); // Handle text which might contain special chars std::string parseShortName(meshtastic_NodeInfoLite *node); // Get the shortname, or a substitute if has unprintable chars - bool isPrintable(std::string); // Check for characters which the font can't print + bool isPrintable(const std::string &text); // Check for characters which the font can't print // Convenient references diff --git a/src/graphics/niche/InkHUD/AppletFont.cpp b/src/graphics/niche/InkHUD/AppletFont.cpp index 93a621ee8..188671a0e 100644 --- a/src/graphics/niche/InkHUD/AppletFont.cpp +++ b/src/graphics/niche/InkHUD/AppletFont.cpp @@ -39,11 +39,11 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding // Caution: signed and unsigned types int8_t glyphAscender = 0 - gfxFont->glyph[i].yOffset; if (glyphAscender > 0) - this->ascenderHeight = max(this->ascenderHeight, (uint8_t)glyphAscender); + this->ascenderHeight = max(this->ascenderHeight, static_cast(glyphAscender)); int8_t glyphDescender = gfxFont->glyph[i].height + gfxFont->glyph[i].yOffset; if (glyphDescender > 0) - this->descenderHeight = max(this->descenderHeight, (uint8_t)glyphDescender); + this->descenderHeight = max(this->descenderHeight, static_cast(glyphDescender)); } // Apply any manual padding to grow or shrink the line size @@ -52,7 +52,7 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding descenderHeight += paddingBottom; // Find how far the cursor advances when we "print" a space character - spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance; + spaceCharWidth = gfxFont->glyph[static_cast(' ') - gfxFont->first].xAdvance; } /* @@ -98,7 +98,7 @@ uint8_t InkHUD::AppletFont::widthBetweenWords() // Convert a unicode char from set of UTF-8 bytes to UTF-32 // Used by AppletFont::applyEncoding, which remaps unicode chars for extended ASCII fonts, based on their UTF-32 value -uint32_t InkHUD::AppletFont::toUtf32(std::string utf8) +uint32_t InkHUD::AppletFont::toUtf32(const std::string &utf8) { uint32_t utf32 = 0; @@ -132,7 +132,7 @@ uint32_t InkHUD::AppletFont::toUtf32(std::string utf8) // Process a string, collating UTF-8 bytes, and sending them off for re-encoding to extended ASCII // Not all InkHUD text is passed through here, only text which could potentially contain non-ASCII chars -std::string InkHUD::AppletFont::decodeUTF8(std::string encoded) +std::string InkHUD::AppletFont::decodeUTF8(const std::string &encoded) { // Final processed output std::string decoded; @@ -141,7 +141,7 @@ std::string InkHUD::AppletFont::decodeUTF8(std::string encoded) std::string utf8Char; uint8_t utf8CharSize = 0; - for (char &c : encoded) { + for (const char &c : encoded) { // If first byte if (utf8Char.empty()) { @@ -178,7 +178,7 @@ std::string InkHUD::AppletFont::decodeUTF8(std::string encoded) // Re-encode a single UTF-8 character to extended ASCII // Target encoding depends on the font -char InkHUD::AppletFont::applyEncoding(std::string utf8) +char InkHUD::AppletFont::applyEncoding(const std::string &utf8) { // ##################################################### Syntactic Sugar ##################################################### #define REMAP(in, out) \ diff --git a/src/graphics/niche/InkHUD/AppletFont.h b/src/graphics/niche/InkHUD/AppletFont.h index 02ba13c31..8374c7f61 100644 --- a/src/graphics/niche/InkHUD/AppletFont.h +++ b/src/graphics/niche/InkHUD/AppletFont.h @@ -30,20 +30,21 @@ class AppletFont }; AppletFont(); - AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding = ASCII, int8_t paddingTop = 0, int8_t paddingBottom = 0); + explicit AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding = ASCII, int8_t paddingTop = 0, + int8_t paddingBottom = 0); uint8_t lineHeight(); uint8_t heightAboveCursor(); uint8_t heightBelowCursor(); uint8_t widthBetweenWords(); // Width of the space character - std::string decodeUTF8(std::string encoded); + std::string decodeUTF8(const std::string &encoded); - const GFXfont *gfxFont = NULL; // Default value: in-built AdafruitGFX font + const GFXfont *gfxFont = nullptr; // Default value: in-built AdafruitGFX font private: - uint32_t toUtf32(std::string utf8); - char applyEncoding(std::string utf8); + uint32_t toUtf32(const std::string &utf8); + char applyEncoding(const std::string &utf8); uint8_t height = 8; // Default value: in-built AdafruitGFX font uint8_t ascenderHeight = 0; // Default value: in-built AdafruitGFX font diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp index 9794c3efb..a063c08b5 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp @@ -81,25 +81,25 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke uint8_t InkHUD::NodeListApplet::maxCards() { // Cache result. Shouldn't change during execution - static uint8_t cards = 0; + static uint8_t maxCardCount = 0; - if (!cards) { + if (!maxCardCount) { const uint16_t height = Tile::maxDisplayDimension(); // Use a loop instead of arithmetic, because it's easier for my brain to follow // Add cards one by one, until the latest card extends below screen uint16_t y = cardH; // First card: no margin above - cards = 1; + maxCardCount = 1; while (y < height) { y += cardMarginH; y += cardH; - cards++; + maxCardCount++; } } - return cards; + return maxCardCount; } // Draw, using info which derived applet placed into NodeListApplet::cards for us @@ -137,12 +137,12 @@ void InkHUD::NodeListApplet::onRender(bool full) // Gather info // ======================================== - NodeNum &nodeNum = card->nodeNum; + const NodeNum &nodeNum = card->nodeNum; SignalStrength &signal = card->signal; std::string longName; // handled below std::string shortName; // handled below std::string distance; // handled below; - uint8_t &hopsAway = card->hopsAway; + const uint8_t &hopsAway = card->hopsAway; meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum); diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp index 4fd01c348..c0850b742 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp @@ -29,10 +29,10 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta // If we get a different type of status, something has gone weird elsewhere assert(status->getStatusType() == STATUS_TYPE_POWER); - meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)status; + const meshtastic::PowerStatus *pwrStatus = (const meshtastic::PowerStatus *)status; // Get the new state of charge %, and round to the nearest 10% - uint8_t newSocRounded = ((powerStatus->getBatteryChargePercent() + 5) / 10) * 10; + uint8_t newSocRounded = ((pwrStatus->getBatteryChargePercent() + 5) / 10) * 10; // If rounded value has changed, trigger a display update // It's okay to requestUpdate before we store the new value, as the update won't run until next loop() diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 6a141f73e..26d6f03d3 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -1176,7 +1176,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) items.push_back(MenuItem("Back", previousPage)); for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { - meshtastic_Channel &ch = channels.getByIndex(i); + const meshtastic_Channel &ch = channels.getByIndex(i); if (!ch.has_settings) continue; @@ -1252,7 +1252,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) case NODE_CONFIG_CHANNEL_PRECISION: { previousPage = MenuPage::NODE_CONFIG_CHANNEL_DETAIL; items.push_back(MenuItem("Back", previousPage)); - meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); + const meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) { items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); break; @@ -1759,7 +1759,7 @@ void InkHUD::MenuApplet::populateRecipientPage() for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { // Get the channel, and check if it's enabled - meshtastic_Channel &channel = channels.getByIndex(i); + const meshtastic_Channel &channel = channels.getByIndex(i); if (!channel.has_settings || channel.role == meshtastic_Channel_Role_DISABLED) continue; @@ -1829,7 +1829,7 @@ void InkHUD::MenuApplet::populateRecipientPage() items.push_back(MenuItem("Exit", MenuPage::EXIT)); } -void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, std::string text) +void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, const std::string &text) { setFont(fontSmall); uint16_t wrapMaxH = 0; diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h index 7b092153b..b5c1c86e4 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h @@ -55,7 +55,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, - std::string text); // Draw input field for free text + const std::string &text); // Draw input field for free text uint16_t getSystemInfoPanelHeight(); void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width, uint16_t *height = nullptr); // Info panel at top of root menu diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp index 19cef4fbd..6c8069c8b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp @@ -228,17 +228,17 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila Notification::Type::NOTIFICATION_MESSAGE_BROADCAST)) { // Although we are handling DM and broadcast notifications together, we do need to treat them slightly differently - bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST; + bool msgIsBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST; // Pick source of message - MessageStore::Message *message = - isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm; + const MessageStore::Message *message = + msgIsBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm; // Find info about the sender meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender); // Leading tag (channel vs. DM) - text += isBroadcast ? "From:" : "DM: "; + text += msgIsBroadcast ? "From:" : "DM: "; // Sender id if (node && node->has_user) @@ -252,7 +252,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila text.clear(); // Leading tag (channel vs. DM) - text += isBroadcast ? "Msg from " : "DM from "; + text += msgIsBroadcast ? "Msg from " : "DM from "; // Sender id if (node && node->has_user) diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp index a09ff55d5..54515b296 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp @@ -55,12 +55,12 @@ int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *sta // We'll mimic that behavior, just to keep in line with the other Statuses, // even though I'm not sure what the original reason for jumping through these extra hoops was. assert(status->getStatusType() == STATUS_TYPE_BLUETOOTH); - meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)status; + const auto *btStatus = static_cast(status); // When pairing begins - if (bluetoothStatus->getConnectionState() == meshtastic::BluetoothStatus::ConnectionState::PAIRING) { + if (btStatus->getConnectionState() == meshtastic::BluetoothStatus::ConnectionState::PAIRING) { // Store the passkey for rendering - passkey = bluetoothStatus->getPasskey(); + passkey = btStatus->getPasskey(); // Show pairing screen bringToForeground(); diff --git a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp index 5a659c606..a7fd094e6 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp @@ -69,9 +69,10 @@ void InkHUD::HeardApplet::populateFromNodeDB() } // Sort the collection by age - std::sort(ordered.begin(), ordered.end(), [](meshtastic_NodeInfoLite *top, meshtastic_NodeInfoLite *bottom) -> bool { - return (top->last_heard > bottom->last_heard); - }); + std::sort(ordered.begin(), ordered.end(), + [](const meshtastic_NodeInfoLite *top, const meshtastic_NodeInfoLite *bottom) -> bool { + return (top->last_heard > bottom->last_heard); + }); // Keep the most recent entries only // Just enough to fill the screen diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp index f16721357..01bdc2224 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp @@ -69,7 +69,7 @@ void InkHUD::ThreadedMessageApplet::onRender(bool full) while (msgB >= (0 - fontSmall.lineHeight()) && i < store->messages.size()) { // Grab data for message - MessageStore::Message &m = store->messages.at(i); + const MessageStore::Message &m = store->messages.at(i); bool outgoing = (m.sender == 0) || (m.sender == myNodeInfo.my_node_num); // Own NodeNum if canned message std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message diff --git a/src/graphics/niche/InkHUD/MessageStore.cpp b/src/graphics/niche/InkHUD/MessageStore.cpp index 94e0aa661..44a1ef633 100644 --- a/src/graphics/niche/InkHUD/MessageStore.cpp +++ b/src/graphics/niche/InkHUD/MessageStore.cpp @@ -12,7 +12,7 @@ using namespace NicheGraphics; constexpr uint8_t MAX_MESSAGES_SAVED = 10; constexpr uint32_t MAX_MESSAGE_SIZE = 250; -InkHUD::MessageStore::MessageStore(std::string label) +InkHUD::MessageStore::MessageStore(const std::string &label) { filename = ""; filename += "/NicheGraphics"; @@ -50,12 +50,13 @@ void InkHUD::MessageStore::saveToFlash() // For each message for (uint8_t i = 0; i < messages.size() && i < MAX_MESSAGES_SAVED; i++) { Message &m = messages.at(i); - f.write((uint8_t *)&m.timestamp, sizeof(m.timestamp)); // Write timestamp. 4 bytes - f.write((uint8_t *)&m.sender, sizeof(m.sender)); // Write sender NodeId. 4 Bytes - f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex)); // Write channel index. 1 Byte - f.write((uint8_t *)m.text.c_str(), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text. Variable length - f.write('\0'); // Append null term - LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", (uint32_t)i, min(MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str()); + f.write(reinterpret_cast(&m.timestamp), sizeof(m.timestamp)); // Write timestamp. 4 bytes + f.write(reinterpret_cast(&m.sender), sizeof(m.sender)); // Write sender NodeId. 4 Bytes + f.write(reinterpret_cast(&m.channelIndex), sizeof(m.channelIndex)); // Write channel index. 1 Byte + f.write(reinterpret_cast(m.text.c_str()), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text + f.write('\0'); // Append null term + LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast(i), min(MAX_MESSAGE_SIZE, m.text.size()), + m.text.c_str()); } // Release firmware's SPI lock, because SafeFile::close needs it @@ -111,17 +112,17 @@ void InkHUD::MessageStore::loadFromFlash() // First byte: how many messages are in the flash store uint8_t flashMessageCount = 0; - f.readBytes((char *)&flashMessageCount, 1); - LOG_DEBUG("Messages available: %u", (uint32_t)flashMessageCount); + f.readBytes(reinterpret_cast(&flashMessageCount), 1); + LOG_DEBUG("Messages available: %u", static_cast(flashMessageCount)); // For each message for (uint8_t i = 0; i < flashMessageCount && i < MAX_MESSAGES_SAVED; i++) { Message m; // Read meta data (fixed width) - f.readBytes((char *)&m.timestamp, sizeof(m.timestamp)); - f.readBytes((char *)&m.sender, sizeof(m.sender)); - f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex)); + f.readBytes(reinterpret_cast(&m.timestamp), sizeof(m.timestamp)); + f.readBytes(reinterpret_cast(&m.sender), sizeof(m.sender)); + f.readBytes(reinterpret_cast(&m.channelIndex), sizeof(m.channelIndex)); // Read characters until we find a null term char c; @@ -136,7 +137,8 @@ void InkHUD::MessageStore::loadFromFlash() // Store in RAM messages.push_back(m); - LOG_DEBUG("#%u, timestamp=%u, sender(num)=%u, text=\"%s\"", (uint32_t)i, m.timestamp, m.sender, m.text.c_str()); + LOG_DEBUG("#%u, timestamp=%u, sender(num)=%u, text=\"%s\"", static_cast(i), m.timestamp, m.sender, + m.text.c_str()); } f.close(); diff --git a/src/graphics/niche/InkHUD/MessageStore.h b/src/graphics/niche/InkHUD/MessageStore.h index 745c3b2eb..55fb9b8cc 100644 --- a/src/graphics/niche/InkHUD/MessageStore.h +++ b/src/graphics/niche/InkHUD/MessageStore.h @@ -31,7 +31,7 @@ class MessageStore }; MessageStore() = delete; - explicit MessageStore(std::string label); // Label determines filename in flash + explicit MessageStore(const std::string &label); // Label determines filename in flash void saveToFlash(); void loadFromFlash(); diff --git a/src/graphics/niche/InkHUD/Renderer.cpp b/src/graphics/niche/InkHUD/Renderer.cpp index 89a83c932..a73e209ff 100644 --- a/src/graphics/niche/InkHUD/Renderer.cpp +++ b/src/graphics/niche/InkHUD/Renderer.cpp @@ -269,42 +269,42 @@ void InkHUD::Renderer::clearTile(Tile *t) // Rotate the tile dimensions int16_t left = 0; int16_t top = 0; - uint16_t width = 0; - uint16_t height = 0; + uint16_t tileW = 0; + uint16_t tileH = 0; switch (settings->rotation) { case 0: left = t->getLeft(); top = t->getTop(); - width = t->getWidth(); - height = t->getHeight(); + tileW = t->getWidth(); + tileH = t->getHeight(); break; case 1: left = driver->width - (t->getTop() + t->getHeight()); top = t->getLeft(); - width = t->getHeight(); - height = t->getWidth(); + tileW = t->getHeight(); + tileH = t->getWidth(); break; case 2: left = driver->width - (t->getLeft() + t->getWidth()); top = driver->height - (t->getTop() + t->getHeight()); - width = t->getWidth(); - height = t->getHeight(); + tileW = t->getWidth(); + tileH = t->getHeight(); break; case 3: left = t->getTop(); top = driver->height - (t->getLeft() + t->getWidth()); - width = t->getHeight(); - height = t->getWidth(); + tileW = t->getHeight(); + tileH = t->getWidth(); break; } // Calculate the bounds to clear uint16_t xStart = (left < 0) ? 0 : left; uint16_t yStart = (top < 0) ? 0 : top; - if (xStart >= driver->width || yStart >= driver->height || left + width < 0 || top + height < 0) + if (xStart >= driver->width || yStart >= driver->height || left + tileW < 0 || top + tileH < 0) return; // the box is completely off the screen - uint16_t xEnd = left + width; - uint16_t yEnd = top + height; + uint16_t xEnd = left + tileW; + uint16_t yEnd = top + tileH; if (xEnd > driver->width) xEnd = driver->width; if (yEnd > driver->height) diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index cec72ce8f..a80b468d5 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -396,7 +396,7 @@ void InkHUD::WindowManager::autoshow() { // Don't perform autoshow if a system applet has exclusive use of the display right now // Note: lockRequests prevents autoshow attempting to hide menuApplet - for (SystemApplet *sa : inkhud->systemApplets) { + for (const SystemApplet *sa : inkhud->systemApplets) { if (sa->lockRendering || sa->lockRequests) return; } diff --git a/src/graphics/niche/Utils/CannedMessageStore.cpp b/src/graphics/niche/Utils/CannedMessageStore.cpp index 50998930d..182b7e1f8 100644 --- a/src/graphics/niche/Utils/CannedMessageStore.cpp +++ b/src/graphics/niche/Utils/CannedMessageStore.cpp @@ -146,7 +146,7 @@ void CannedMessageStore::handleGet(meshtastic_AdminMessage *response) std::string merged; if (!messages.empty()) { // Don't run if no messages: error on pop_back with size=0 merged.reserve(201); - for (std::string &s : messages) { + for (const std::string &s : messages) { merged += s; merged += '|'; } diff --git a/src/input/TCA8418Keyboard.cpp b/src/input/TCA8418Keyboard.cpp index bd8338acf..e0321b462 100644 --- a/src/input/TCA8418Keyboard.cpp +++ b/src/input/TCA8418Keyboard.cpp @@ -63,7 +63,6 @@ void TCA8418Keyboard::pressed(uint8_t key) if (state == Init || state == Busy) { return; } - uint8_t next_key = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; @@ -72,7 +71,7 @@ void TCA8418Keyboard::pressed(uint8_t key) } // Compute key index based on dynamic row/column - next_key = row * _TCA8418_COLS + col; + next_key = (int8_t)(row * _TCA8418_COLS + col); // LOG_DEBUG("TCA8418: Key %u -> Next Key %u", key, next_key); diff --git a/src/main.cpp b/src/main.cpp index d5e652a95..f70b3975e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -248,9 +248,8 @@ uint32_t timeLastPowered = 0; static OSThread *powerFSMthread; OSThread *ambientLightingThread; -#ifdef ARCH_PORTDUINO +RadioInterface *rIf = NULL; RadioLibHal *RadioLibHAL = NULL; -#endif /** * Some platforms (nrf52) might provide an alterate version that suppresses calling delay from sleep. @@ -724,17 +723,15 @@ void setup() playStartMelody(); #if HAS_SCREEN - // fixed screen override? - if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) - screen_model = config.display.oled; - + // fixed screen override? #if defined(USE_SH1107) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128 screen_geometry = GEOMETRY_128_128; -#endif - -#if defined(USE_SH1107_128_64) +#elif defined(USE_SH1107_128_64) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64 +#else + if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) + screen_model = config.display.oled; #endif #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 913d45b41..a52fa4478 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -814,26 +814,28 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; moduleConfig.has_external_notification = true; -#if defined(PIN_BUZZER) +#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) moduleConfig.external_notification.enabled = true; +#endif +#if defined(PIN_BUZZER) moduleConfig.external_notification.output_buzzer = PIN_BUZZER; moduleConfig.external_notification.use_pwm = true; moduleConfig.external_notification.alert_message_buzzer = true; - moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif #if defined(PIN_VIBRATION) - moduleConfig.external_notification.enabled = true; moduleConfig.external_notification.output_vibra = PIN_VIBRATION; moduleConfig.external_notification.alert_message_vibra = true; moduleConfig.external_notification.output_ms = 500; - moduleConfig.external_notification.nag_timeout = 2; #endif #if defined(LED_NOTIFICATION) - moduleConfig.external_notification.enabled = true; moduleConfig.external_notification.output = LED_NOTIFICATION; moduleConfig.external_notification.active = LED_STATE_ON; moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 1000; +#endif +#if defined(PIN_VIBRATION) + moduleConfig.external_notification.nag_timeout = 2; +#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 34393d259..845a936d4 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -439,7 +439,7 @@ void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, cons } // Getters and setters for hop limit fields packed in hop_limit -inline uint8_t PacketHistory::getHighestHopLimit(PacketRecord &r) +inline uint8_t PacketHistory::getHighestHopLimit(const PacketRecord &r) { return r.hop_limit & HOP_LIMIT_HIGHEST_MASK; } @@ -449,7 +449,7 @@ inline void PacketHistory::setHighestHopLimit(PacketRecord &r, uint8_t hopLimit) r.hop_limit = (r.hop_limit & ~HOP_LIMIT_HIGHEST_MASK) | (hopLimit & HOP_LIMIT_HIGHEST_MASK); } -inline uint8_t PacketHistory::getOurTxHopLimit(PacketRecord &r) +inline uint8_t PacketHistory::getOurTxHopLimit(const PacketRecord &r) { return (r.hop_limit & HOP_LIMIT_OUR_TX_MASK) >> HOP_LIMIT_OUR_TX_SHIFT; } diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 5fbad2dc9..9b6a93280 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -43,9 +43,9 @@ class PacketHistory * @return true if node was indeed a relayer, false if not */ bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr); - uint8_t getHighestHopLimit(PacketRecord &r); + uint8_t getHighestHopLimit(const PacketRecord &r); void setHighestHopLimit(PacketRecord &r, uint8_t hopLimit); - uint8_t getOurTxHopLimit(PacketRecord &r); + uint8_t getOurTxHopLimit(const PacketRecord &r); void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit); PacketHistory(const PacketHistory &); // non construction-copyable diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index ab3e1d8a0..e8202d9b0 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -237,9 +237,9 @@ std::unique_ptr initLoRa() std::unique_ptr rIf = nullptr; #if ARCH_PORTDUINO - SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); + SPISettings loraSpiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); #else - SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); + SPISettings loraSpiSettings(4000000, MSBFIRST, SPI_MODE0); #endif #ifdef ARCH_PORTDUINO @@ -280,7 +280,7 @@ std::unique_ptr initLoRa() delete RadioLibHAL; RadioLibHAL = nullptr; } - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + RadioLibHAL = new LockingArduinoHal(SPI, loraSpiSettings); } rIf = loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin, @@ -295,16 +295,18 @@ std::unique_ptr initLoRa() } #elif defined(HW_SPI1_DEVICE) - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); + LockingArduinoHal *loraHal = new LockingArduinoHal(SPI1, loraSpiSettings); + RadioLibHAL = loraHal; #else // HW_SPI1_DEVICE - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *loraHal = new LockingArduinoHal(SPI, loraSpiSettings); + RadioLibHAL = loraHal; #endif // radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) #if defined(USE_STM32WLx) if (!rIf) { rIf = std::unique_ptr( - new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); + new STM32WLE5JCInterface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No STM32WL radio"); rIf = nullptr; @@ -317,7 +319,7 @@ std::unique_ptr initLoRa() #if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = std::unique_ptr(new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1)); + rIf = std::unique_ptr(new RF95Interface(loraHal, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1)); if (!rIf->init()) { LOG_WARN("No RF95 radio"); rIf = nullptr; @@ -331,7 +333,7 @@ std::unique_ptr initLoRa() #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { auto sxIf = - std::unique_ptr(new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); + std::unique_ptr(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); #ifdef SX126X_DIO3_TCXO_VOLTAGE sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); #endif @@ -350,7 +352,7 @@ std::unique_ptr initLoRa() if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // try using the specified TCXO voltage auto sxIf = - std::unique_ptr(new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); + std::unique_ptr(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); if (!sxIf->init()) { LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); @@ -364,8 +366,7 @@ std::unique_ptr initLoRa() if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead - rIf = - std::unique_ptr(new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); + rIf = std::unique_ptr(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); rIf = nullptr; @@ -381,7 +382,7 @@ std::unique_ptr initLoRa() if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // try using the specified TCXO voltage auto sxIf = - std::unique_ptr(new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); + std::unique_ptr(new SX1268Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); if (!sxIf->init()) { LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); @@ -394,8 +395,7 @@ std::unique_ptr initLoRa() } #endif if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = - std::unique_ptr(new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); + rIf = std::unique_ptr(new SX1268Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1268 radio"); rIf = nullptr; @@ -408,8 +408,7 @@ std::unique_ptr initLoRa() #if defined(USE_LLCC68) if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = - std::unique_ptr(new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); + rIf = std::unique_ptr(new LLCC68Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No LLCC68 radio"); rIf = nullptr; @@ -423,7 +422,7 @@ std::unique_ptr initLoRa() #if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { rIf = std::unique_ptr( - new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN)); + new LR1110Interface(loraHal, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1110 radio"); rIf = nullptr; @@ -437,7 +436,7 @@ std::unique_ptr initLoRa() #if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 if (!rIf) { rIf = std::unique_ptr( - new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN)); + new LR1120Interface(loraHal, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1120 radio"); rIf = nullptr; @@ -451,7 +450,7 @@ std::unique_ptr initLoRa() #if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 if (!rIf) { rIf = std::unique_ptr( - new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN)); + new LR1121Interface(loraHal, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1121 radio"); rIf = nullptr; @@ -464,8 +463,7 @@ std::unique_ptr initLoRa() #if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 if (!rIf) { - rIf = - std::unique_ptr(new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY)); + rIf = std::unique_ptr(new SX1280Interface(loraHal, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1280 radio"); rIf = nullptr; diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 20026767e..2d9230a21 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -27,7 +27,7 @@ int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) /** * Read any rx chars from the link and call handleRecStream */ -int32_t StreamAPI::readStream(char *buf, uint16_t bufLen) +int32_t StreamAPI::readStream(const char *buf, uint16_t bufLen) { if (bufLen < 1) { // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time @@ -56,7 +56,7 @@ void StreamAPI::writeStream() } } -int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen) +int32_t StreamAPI::handleRecStream(const char *buf, uint16_t bufLen) { uint16_t index = 0; while (bufLen > index) { // Currently we never want to block diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 4ca2c197f..97e231f23 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -57,8 +57,8 @@ class StreamAPI : public PhoneAPI * Read any rx chars from the link and call handleToRadio */ int32_t readStream(); - int32_t readStream(char *buf, uint16_t bufLen); - int32_t handleRecStream(char *buf, uint16_t bufLen); + int32_t readStream(const char *buf, uint16_t bufLen); + int32_t handleRecStream(const char *buf, uint16_t bufLen); /** * call getFromRadio() and deliver encapsulated packets to the Stream diff --git a/src/mesh/aes-ccm.cpp b/src/mesh/aes-ccm.cpp index 420d80e9a..5ed7ff928 100644 --- a/src/mesh/aes-ccm.cpp +++ b/src/mesh/aes-ccm.cpp @@ -33,7 +33,7 @@ static int constant_time_compare(const void *a_, const void *b_, size_t len) d |= (a[i] ^ b[i]); } /* Constant time bit arithmetic to convert d > 0 to -1 and d = 0 to 0. */ - return (1 & ((d - 1) >> 8)) - 1; + return (1 & (((unsigned int)d - 1) >> 8)) - 1; } static void WPA_PUT_BE16(uint8_t *a, uint16_t val) diff --git a/src/mesh/api/PacketAPI.h b/src/mesh/api/PacketAPI.h index fc08ab209..357eb05c2 100644 --- a/src/mesh/api/PacketAPI.h +++ b/src/mesh/api/PacketAPI.h @@ -17,7 +17,7 @@ class PacketAPI : public PhoneAPI, public concurrency::OSThread virtual int32_t runOnce(); protected: - PacketAPI(PacketServer *_server); + explicit PacketAPI(PacketServer *_server); // Check the current underlying physical queue to see if the client is fetching packets bool checkIsConnected() override; diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 912cb467e..281ece464 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -623,7 +623,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) } // Helper lambda to create JSON array and clean up memory properly - auto createJSONArrayFromLog = [](uint32_t *logArray, int count) -> JSONValue * { + auto createJSONArrayFromLog = [](const uint32_t *logArray, int count) -> JSONValue * { JSONArray tempArray; for (int i = 0; i < count; i++) { tempArray.push_back(new JSONValue((int)logArray[i])); diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 7d7b3cdb1..c7eb1b15b 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1209,14 +1209,13 @@ int32_t CannedMessageModule::runOnce() this->cursor = 0; // Tell Screen to jump straight to the TextMessage frame - UIFrameEvent e; e.action = UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE; this->notifyObservers(&e); // Now deactivate this module this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - return INT32_MAX; // don’t fall back into canned list + return INT32_MAX; // don't fall back into canned list } else { this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } @@ -1237,14 +1236,13 @@ int32_t CannedMessageModule::runOnce() this->cursor = 0; // Tell Screen to jump straight to the TextMessage frame - UIFrameEvent e; e.action = UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE; this->notifyObservers(&e); // Now deactivate this module this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - return INT32_MAX; // don’t fall back into canned list + return INT32_MAX; // don't fall back into canned list } } else { this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; @@ -1255,11 +1253,10 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; this->cursor = 0; - UIFrameEvent e; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; this->notifyObservers(&e); - // Immediately stop, don’t linger on canned screen + // Immediately stop, don't linger on canned screen return INT32_MAX; } // Highlight [Select Destination] initially when entering the message list @@ -2070,7 +2067,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st // Draw lines with emotes int rowHeight = FONT_HEIGHT_SMALL; int yLine = inputY; - for (auto &line : lines) { + for (const auto &line : lines) { int nextX = x; for (const auto &token : line) { if (token.first) { diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 0301b2ac3..cc7124f0e 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -367,7 +367,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); + const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex()); // If we receive a broadcast message, apply channel mute setting diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 457935688..f828f4a16 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -23,7 +23,6 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) { switch (arg->getStatusType()) { case STATUS_TYPE_POWER: { - meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg; if (powerStatus->getHasUSB() || powerStatus->getIsCharging()) { power_state = charging; if (powerStatus->getBatteryChargePercent() >= 100) { @@ -39,7 +38,6 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) break; } case STATUS_TYPE_BLUETOOTH: { - meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)arg; switch (bluetoothStatus->getConnectionState()) { case meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED: { ble_state = unpaired; @@ -199,33 +197,30 @@ void StatusLEDModule::setPowerLED(bool LEDon) PMU->setChargingLedMode(LEDon ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); } #endif - if (LEDon) - LEDon = LED_STATE_ON; - else - LEDon = LED_STATE_OFF; + uint8_t ledState = LEDon ? LED_STATE_ON : LED_STATE_OFF; #ifdef PCA_LED_POWER - io.digitalWrite(PCA_LED_POWER, LEDon); + io.digitalWrite(PCA_LED_POWER, ledState); #endif #ifdef PCA_LED_ENABLE - io.digitalWrite(PCA_LED_ENABLE, LEDon); + io.digitalWrite(PCA_LED_ENABLE, ledState); #endif #ifdef LED_POWER - digitalWrite(LED_POWER, LEDon); + digitalWrite(LED_POWER, ledState); #endif #ifdef LED_PAIRING - digitalWrite(LED_PAIRING, LEDon); + digitalWrite(LED_PAIRING, ledState); #endif #ifdef Battery_LED_1 - digitalWrite(Battery_LED_1, LEDon); + digitalWrite(Battery_LED_1, ledState); #endif #ifdef Battery_LED_2 - digitalWrite(Battery_LED_2, LEDon); + digitalWrite(Battery_LED_2, ledState); #endif #ifdef Battery_LED_3 - digitalWrite(Battery_LED_3, LEDon); + digitalWrite(Battery_LED_3, ledState); #endif #ifdef Battery_LED_4 - digitalWrite(Battery_LED_4, LEDon); + digitalWrite(Battery_LED_4, ledState); #endif } diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 023a1c798..135d9f6eb 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -131,9 +131,7 @@ void StoreForwardModule::historySend(uint32_t secAgo, uint32_t to) uint32_t StoreForwardModule::getNumAvailablePackets(NodeNum dest, uint32_t last_time) { uint32_t count = 0; - if (lastRequest.find(dest) == lastRequest.end()) { - lastRequest.emplace(dest, 0); - } + lastRequest.emplace(dest, 0); for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) { if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) { // Client is only interested in packets not from itself and only in broadcast packets or packets towards it. diff --git a/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h index 37d909d71..b7029986b 100644 --- a/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h +++ b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h @@ -8,7 +8,7 @@ static std::forward_list sensors; -template void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type) +template void addSensor(const ScanI2C *i2cScanner, ScanI2C::DeviceType type) { ScanI2C::FoundDevice dev = i2cScanner->find(type); if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) { diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index a752f2ab8..069931e21 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -86,7 +86,7 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) return false; } - auto read16 = [](uint8_t *data, uint8_t idx) -> uint16_t { return (data[idx] << 8) | data[idx + 1]; }; + auto read16 = [](const uint8_t *data, uint8_t idx) -> uint16_t { return (data[idx] << 8) | data[idx + 1]; }; computedChecksum = 0; diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp index 6572ef9b1..4113de315 100644 --- a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp @@ -111,7 +111,7 @@ bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement) bool dataReady; error = scd4x.getDataReadyStatus(dataReady); - if (!dataReady) { + if (error != SCD4X_NO_ERROR || !dataReady) { #if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) reClockI2C(currentClock, _bus, false); #endif @@ -419,7 +419,7 @@ bool SCD4XSensor::setTemperature(float tempReference) LOG_INFO("%s: Setting reference temperature at: %.2f", sensorName, tempReference); error = scd4x.getDataReadyStatus(dataReady); - if (!dataReady) { + if (error != SCD4X_NO_ERROR || !dataReady) { LOG_ERROR("%s: Data is not ready", sensorName); return false; } diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp index 0a9db4dff..49d5a5f94 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -182,7 +182,7 @@ uint8_t SEN5XSensor::readBuffer(uint8_t *buffer, uint8_t byteNumber) return receivedBytes; } -uint8_t SEN5XSensor::sen5xCRC(uint8_t *buffer) +uint8_t SEN5XSensor::sen5xCRC(const uint8_t *buffer) { // This code is based on Sensirion's own implementation // https://github.com/Sensirion/arduino-core/blob/41fd02cacf307ec4945955c58ae495e56809b96c/src/SensirionCrc.cpp diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.h b/src/modules/Telemetry/Sensor/SEN5XSensor.h index 46f8c70e9..ef5ad5c29 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.h +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.h @@ -114,7 +114,7 @@ See: https://sensirion.com/resource/application_note/low_power_mode/sen5x bool sendCommand(uint16_t command); bool sendCommand(uint16_t command, uint8_t *buffer, uint8_t byteNumber = 0); uint8_t readBuffer(uint8_t *buffer, uint8_t byteNumber); // Return number of bytes received - uint8_t sen5xCRC(uint8_t *buffer); + uint8_t sen5xCRC(const uint8_t *buffer); bool startCleaning(); uint8_t getMeasurements(); // bool readRawValues(); diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index 41dc02cd1..3371c405d 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -266,7 +266,7 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti } } -void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) +void TraceRouteModule::updateNextHops(const meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) { // E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D // Similarly, if we are C, we can set D as next-hop for D diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index a40ed7733..db94b9d9b 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -62,7 +62,7 @@ class TraceRouteModule : public ProtobufModule, void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly); // Update next-hops in the routing table based on the returned route - void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r); + void updateNextHops(const meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r); // Helper to update next-hop for a single node void maybeSetNextHop(NodeNum target, uint8_t nextHopByte); diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index 4ca074723..20a3c59cc 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -75,7 +75,7 @@ bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc) return true; } -bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method) +bool checkOTACapability(const esp_app_desc_t *app_desc, uint8_t method) { // Combined loader supports all (both) transports, BLE and WiFi if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) { diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index 7c158775f..ce5a7e86b 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -16,7 +16,7 @@ void initialize(); bool isUpdated(); const esp_partition_t *getAppPartition(); bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc); -bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method); +bool checkOTACapability(const esp_app_desc_t *app_desc, uint8_t method); void recoverConfig(meshtastic_Config_NetworkConfig *network); void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash); bool trySwitchToOTA(); diff --git a/src/platform/nrf52/AsyncUDP.h b/src/platform/nrf52/AsyncUDP.h index 718277309..ecbcb12fb 100644 --- a/src/platform/nrf52/AsyncUDP.h +++ b/src/platform/nrf52/AsyncUDP.h @@ -38,7 +38,7 @@ class AsyncUDP : public Print, private concurrency::OSThread class AsyncUDPPacket { public: - AsyncUDPPacket(EthernetUDP &source); + explicit AsyncUDPPacket(EthernetUDP &source); IPAddress remoteIP(); uint16_t length(); diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 9579bef45..3f0b2147c 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -496,7 +496,7 @@ void portduinoSetup() randomSeed(time(NULL)); std::string defaultGpioChipName = gpioChipName + std::to_string(portduino_config.lora_default_gpiochip); - for (auto i : portduino_config.all_pins) { + for (const auto *i : portduino_config.all_pins) { if (i->enabled && i->pin > max_GPIO) max_GPIO = i->pin; } @@ -510,7 +510,7 @@ void portduinoSetup() // Need to bind all the configured GPIO pins so they're not simulated // TODO: If one of these fails, we should log and terminate - for (auto i : portduino_config.all_pins) { + for (const auto *i : portduino_config.all_pins) { // In the case of a ch341 Lora device, we don't want to touch the system GPIO lines for Lora // Those GPIO are handled in our usermode driver instead. if (i->config_section == "Lora" && portduino_config.lora_spi_dev == "ch341") { @@ -572,7 +572,7 @@ void portduinoSetup() return; } -int initGPIOPin(int pinNum, const std::string gpioChipName, int line) +int initGPIOPin(int pinNum, const std::string &gpioChipName, int line) { #ifdef PORTDUINO_LINUX_HARDWARE std::string gpio_name = "GPIO" + std::to_string(pinNum); @@ -643,7 +643,7 @@ bool loadConfig(const char *configPath) if (yamlConfig["Lora"]) { if (yamlConfig["Lora"]["Module"]) { - for (auto &loraModule : portduino_config.loraModules) { + for (const auto &loraModule : portduino_config.loraModules) { if (yamlConfig["Lora"]["Module"].as("") == loraModule.second) { portduino_config.lora_module = loraModule.first; break; @@ -777,7 +777,7 @@ bool loadConfig(const char *configPath) } if (yamlConfig["Display"]) { - for (auto &screen_name : portduino_config.screen_names) { + for (const auto &screen_name : portduino_config.screen_names) { if (yamlConfig["Display"]["Panel"].as("") == screen_name.second) portduino_config.displayPanel = screen_name.first; } @@ -907,7 +907,7 @@ bool loadConfig(const char *configPath) } if (checkConfigPort) { portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as(-1); - if (portduino_config.api_port != -1 && portduino_config.api_port > 1023 && portduino_config.api_port < 65536) { + if (portduino_config.api_port > 1023 && portduino_config.api_port < 65536) { TCPPort = (portduino_config.api_port); } } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 990803e79..aa8847fd7 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -58,7 +58,7 @@ extern std::ofstream traceFile; extern std::ofstream JSONFile; extern Ch341Hal *ch341Hal; -int initGPIOPin(int pinNum, std::string gpioChipname, int line); +int initGPIOPin(int pinNum, const std::string &gpioChipname, int line); bool loadConfig(const char *configPath); static bool ends_with(std::string_view str, std::string_view suffix); void getMacAddr(uint8_t *dmac); @@ -224,7 +224,7 @@ extern struct portduino_config_struct { out << YAML::Key << "Lora" << YAML::Value << YAML::BeginMap; out << YAML::Key << "Module" << YAML::Value << loraModules[lora_module]; - for (auto lora_pin : all_pins) { + for (const auto *lora_pin : all_pins) { if (lora_pin->config_section == "Lora" && lora_pin->enabled) { out << YAML::Key << lora_pin->config_name << YAML::Value << YAML::BeginMap; out << YAML::Key << "pin" << YAML::Value << lora_pin->pin; @@ -350,11 +350,11 @@ extern struct portduino_config_struct { // Display if (displayPanel != no_screen) { out << YAML::Key << "Display" << YAML::Value << YAML::BeginMap; - for (auto &screen_name : screen_names) { + for (const auto &screen_name : screen_names) { if (displayPanel == screen_name.first) out << YAML::Key << "Module" << YAML::Value << screen_name.second; } - for (auto display_pin : all_pins) { + for (const auto *display_pin : all_pins) { if (display_pin->config_section == "Display" && display_pin->enabled) { out << YAML::Key << display_pin->config_name << YAML::Value << YAML::BeginMap; out << YAML::Key << "pin" << YAML::Value << display_pin->pin; @@ -402,7 +402,7 @@ extern struct portduino_config_struct { case ft5x06: out << YAML::Key << "Module" << YAML::Value << "FT5x06"; } - for (auto touchscreen_pin : all_pins) { + for (const auto *touchscreen_pin : all_pins) { if (touchscreen_pin->config_section == "Touchscreen" && touchscreen_pin->enabled) { out << YAML::Key << touchscreen_pin->config_name << YAML::Value << YAML::BeginMap; out << YAML::Key << "pin" << YAML::Value << touchscreen_pin->pin; @@ -425,7 +425,7 @@ extern struct portduino_config_struct { if (pointerDevice != "") out << YAML::Key << "PointerDevice" << YAML::Value << pointerDevice; - for (auto input_pin : all_pins) { + for (const auto *input_pin : all_pins) { if (input_pin->config_section == "Input" && input_pin->enabled) { out << YAML::Key << input_pin->config_name << YAML::Value << YAML::BeginMap; out << YAML::Key << "pin" << YAML::Value << input_pin->pin; diff --git a/src/platform/portduino/USBHal.h b/src/platform/portduino/USBHal.h index 441f75b10..1725763f2 100644 --- a/src/platform/portduino/USBHal.h +++ b/src/platform/portduino/USBHal.h @@ -29,7 +29,7 @@ class Ch341Hal : public RadioLibHal { public: // default constructor - initializes the base HAL and any needed private members - explicit Ch341Hal(uint8_t spiChannel, std::string serial = "", uint32_t vid = 0x1A86, uint32_t pid = 0x5512, + explicit Ch341Hal(uint8_t spiChannel, const std::string &serial = "", uint32_t vid = 0x1A86, uint32_t pid = 0x5512, uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0) : RadioLibHal(PI_INPUT, PI_OUTPUT, PI_LOW, PI_HIGH, PI_RISING, PI_FALLING) { diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index 37221c103..4c04d0c26 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -43,4 +43,4 @@ lib_ignore = NonBlockingRTTTL libpax build_src_filter = - ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l> \ No newline at end of file + ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l> From 57268bf4ead448f8ff0c2a316c4f64bc413708e3 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Mon, 16 Feb 2026 12:38:36 +0100 Subject: [PATCH 158/223] Feat/add scd30 (#9609) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Merge develop into SCD30 * Add SCD30 class * Fix logging and admin commands * Minor cleanup and logging improvements * Minor formatting issue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Improvements on setTemperature * Fix casting float-uint16_t * Pass 100 for resetting temperature offset * Fix issues pointed out by copilot * Add quick reboot to set interval quicker on scd30 * Change saveState to only happen after boot and minor log changes * Fix missing semicolon in one shot mode log --------- Co-authored-by: Thomas Göttgens Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors --- platformio.ini | 4 + src/configuration.h | 1 + src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 1 + src/modules/Telemetry/AirQualityTelemetry.cpp | 6 + src/modules/Telemetry/Sensor/SCD30Sensor.cpp | 511 ++++++++++++++++++ src/modules/Telemetry/Sensor/SCD30Sensor.h | 53 ++ src/modules/Telemetry/Sensor/SCD4XSensor.cpp | 17 +- src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 31 +- 9 files changed, 598 insertions(+), 29 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/SCD30Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SCD30Sensor.h diff --git a/platformio.ini b/platformio.ini index 12eab550e..1d18b4573 100644 --- a/platformio.ini +++ b/platformio.ini @@ -217,6 +217,8 @@ lib_deps = sensirion/Sensirion I2C SCD4x@1.1.0 # renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x sensirion/Sensirion I2C SFA3x@1.0.0 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30 + sensirion/Sensirion I2C SCD30@1.0.0 ; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) [environmental_extra_no_bsec] @@ -247,3 +249,5 @@ lib_deps = sensirion/Sensirion I2C SCD4x@1.1.0 # renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x sensirion/Sensirion I2C SFA3x@1.0.0 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30 + sensirion/Sensirion I2C SCD30@1.0.0 \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 5ec856f88..53ae30d51 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -244,6 +244,7 @@ along with this program. If not, see . #define BQ25896_ADDR 0x6B #define LTR553ALS_ADDR 0x23 #define SEN5X_ADDR 0x69 +#define SCD30_ADDR 0x61 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 161192766..7b575dd63 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -91,7 +91,8 @@ class ScanI2C CST226SE, SEN5X, SFA30, - CW2015 + CW2015, + SCD30 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 2802854ab..df0fad792 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -557,6 +557,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); + SCAN_SIMPLE_CASE(SCD30_ADDR, SCD30, "SCD30", (uint8_t)addr.address); case CST328_ADDR: // Do we have the CST328 or the CST226SE registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1); diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index d7127bb01..5ffe4d992 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -29,6 +29,9 @@ #if __has_include() #include "Sensor/SFA30Sensor.h" #endif +#if __has_include() +#include "Sensor/SCD30Sensor.h" +#endif void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { @@ -57,6 +60,9 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::SFA30); #endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SCD30); +#endif } int32_t AirQualityTelemetryModule::runOnce() diff --git a/src/modules/Telemetry/Sensor/SCD30Sensor.cpp b/src/modules/Telemetry/Sensor/SCD30Sensor.cpp new file mode 100644 index 000000000..0478b6651 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SCD30Sensor.cpp @@ -0,0 +1,511 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../detect/reClockI2C.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SCD30Sensor.h" + +#define SCD30_NO_ERROR 0 + +SCD30Sensor::SCD30Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SCD30, "SCD30") {} + +bool SCD30Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + LOG_INFO("Init sensor: %s", sensorName); + + _bus = bus; + _address = dev->address.address; + +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + scd30.begin(*_bus, _address); + + if (!startMeasurement()) { + LOG_ERROR("%s: Failed to start periodic measurement", sensorName); +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + if (!getASC(ascActive)) { + LOG_WARN("%s: Could not determine ASC state", sensorName); + } + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + if (state == SCD30_MEASUREMENT) { + status = 1; + } else { + status = 0; + } + + initI2CSensor(); + + return true; +} + +bool SCD30Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + float co2, temperature, humidity; + +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + if (scd30.readMeasurementData(co2, temperature, humidity) != SCD30_NO_ERROR) { + LOG_ERROR("SCD30: Failed to read measurement data."); +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + if (co2 == 0) { + LOG_ERROR("SCD30: Invalid CO₂ reading."); + return false; + } + + measurement->variant.air_quality_metrics.has_co2 = true; + measurement->variant.air_quality_metrics.has_co2_temperature = true; + measurement->variant.air_quality_metrics.has_co2_humidity = true; + measurement->variant.air_quality_metrics.co2 = (uint32_t)co2; + measurement->variant.air_quality_metrics.co2_temperature = temperature; + measurement->variant.air_quality_metrics.co2_humidity = humidity; + + LOG_DEBUG("Got %s readings: co2=%u, co2_temp=%.2f, co2_hum=%.2f", sensorName, (uint32_t)co2, temperature, humidity); + + return true; +} + +bool SCD30Sensor::setMeasurementInterval(uint16_t measInterval) +{ + uint16_t error; + + LOG_INFO("%s: setting measurement interval at %us", sensorName, measInterval); + error = scd30.setMeasurementInterval(measInterval); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to set measurement interval. Error code: %u", sensorName, error); + return false; + } + + // Restart measuring so we don't need to wait the current interval to finish + // (useful when you come from very long intervals) + scd30.stopPeriodicMeasurement(); + scd30.startPeriodicMeasurement(0); + + getMeasurementInterval(measurementInterval); + return true; +} + +bool SCD30Sensor::getMeasurementInterval(uint16_t &measInterval) +{ + uint16_t error; + + LOG_INFO("%s: getting measurement interval", sensorName); + error = scd30.getMeasurementInterval(measInterval); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to get measurement interval. Error code: %u", sensorName, error); + return false; + } + + LOG_INFO("%s: measurement interval is %us", sensorName, measInterval); + + return true; +} + +/** + * @brief Start measurement mode + * @note This function should not change the clock + */ +bool SCD30Sensor::startMeasurement() +{ + uint16_t error; + + if (state == SCD30_MEASUREMENT) { + LOG_DEBUG("%s: Already in measurement mode", sensorName); + return true; + } + + error = scd30.startPeriodicMeasurement(0); + + if (error == SCD30_NO_ERROR) { + LOG_INFO("%s: Started measurement mode", sensorName); + + state = SCD30_MEASUREMENT; + return true; + } else { + LOG_ERROR("%s: Couldn't start measurement mode", sensorName); + return false; + } +} + +/** + * @brief Stop measurement mode + * @note This function should not change the clock + */ +bool SCD30Sensor::stopMeasurement() +{ + uint16_t error; + + error = scd30.stopPeriodicMeasurement(); + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to stop measurement", sensorName); + return false; + } + + state = SCD30_IDLE; + return true; +} + +bool SCD30Sensor::performFRC(uint16_t targetCO2) +{ + uint16_t error; + + LOG_INFO("%s: Issuing FRC. Ensure device has been working at least 3 minutes in stable target environment", sensorName); + + LOG_INFO("%s: Target CO2: %u ppm", sensorName, targetCO2); + error = scd30.forceRecalibration((uint16_t)targetCO2); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to perform forced recalibration.", sensorName); + return false; + } + + LOG_INFO("%s: FRC Correction successful.", sensorName); + + return true; +} + +bool SCD30Sensor::setASC(bool ascEnabled) +{ + uint16_t error; + + LOG_INFO("%s: %s ASC", sensorName, ascEnabled ? "Enabling" : "Disabling"); + + error = scd30.activateAutoCalibration((uint16_t)ascEnabled); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to send command.", sensorName); + return false; + } + + if (!getASC(ascActive)) { + LOG_ERROR("%s: Unable to check if ASC is enabled", sensorName); + return false; + } + + return true; +} + +bool SCD30Sensor::getASC(uint16_t &_ascActive) +{ + uint16_t error; + // LOG_INFO("%s: Getting ASC", sensorName); + + error = scd30.getAutoCalibrationStatus(_ascActive); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to send command.", sensorName); + return false; + } + + LOG_INFO("%s: ASC is %s", sensorName, _ascActive ? "enabled" : "disabled"); + + return true; +} + +/** + * @brief Set the temperature reference. Unit ℃. + * + * The on-board RH/T sensor is influenced by thermal self-heating of SCD30 + * and other electrical components. Design-in alters the thermal properties + * of SCD30 such that temperature and humidity offsets may occur when + * operating the sensor in end-customer devices. Compensation of those + * effects is achievable by writing the temperature offset found in + * continuous operation of the device into the sensor. Temperature offset + * value is saved in non-volatile memory. The last set value will be used + * for temperature offset compensation after repowering. + * + * @param[in] tempReference + * @note this function is certainly confusing and it's not recommended + */ +bool SCD30Sensor::setTemperature(float tempReference) +{ + uint16_t error; + uint16_t updatedTempOffset; + float tempOffset; + uint16_t _tempOffset; + float co2; + float temperature; + float humidity; + + if (tempReference == 100) { + // Requesting the value of 100 will restore the temperature offset + LOG_INFO("%s: Setting reference temperature at 0degC", sensorName); + _tempOffset = 0; + } else { + + LOG_INFO("%s: Setting reference temperature at: %.2f", sensorName, tempReference); + + error = scd30.readMeasurementData(co2, temperature, humidity); + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to read current temperature. Error code: %u", sensorName, error); + return false; + } + + LOG_INFO("%s: Current sensor temperature: %.2f", sensorName, temperature); + + tempOffset = (temperature - tempReference); + if (tempOffset < 0) { + LOG_ERROR("%s temperature offset is only positive", sensorName); + return false; + } + + tempOffset *= 100; + _tempOffset = static_cast(tempOffset); + } + + LOG_INFO("%s: Setting temperature offset: %u (*100)", sensorName, _tempOffset); + + error = scd30.setTemperatureOffset(_tempOffset); + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to set temperature offset. Error code: %u", sensorName, error); + return false; + } + + scd30.getTemperatureOffset(updatedTempOffset); + LOG_INFO("%s: Updated sensor temperature offset: %u (*100)", sensorName, updatedTempOffset); + + return true; +} + +bool SCD30Sensor::setAltitude(uint16_t altitude) +{ + uint16_t error; + + LOG_INFO("%s: setting altitude at %um", sensorName, altitude); + + error = scd30.setAltitudeCompensation(altitude); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to set altitude. Error code: %u", sensorName, error); + return false; + } + + uint16_t newAltitude; + getAltitude(newAltitude); + + return true; +} + +bool SCD30Sensor::getAltitude(uint16_t &altitude) +{ + uint16_t error; + // LOG_INFO("%s: Getting altitude", sensorName); + + error = scd30.getAltitudeCompensation(altitude); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to get altitude. Error code: %u", sensorName, error); + return false; + } + LOG_INFO("%s: Sensor altitude: %u", sensorName, altitude); + + return true; +} + +bool SCD30Sensor::softReset() +{ + uint16_t error; + + LOG_INFO("%s: Requesting soft reset", sensorName); + + error = scd30.softReset(); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to do soft reset. Error code: %u", sensorName, error); + return false; + } + + LOG_INFO("%s: soft reset successful", sensorName); + + return true; +} + +/** + * @brief Check if sensor is in measurement mode + */ +bool SCD30Sensor::isActive() +{ + return state == SCD30_MEASUREMENT; +} + +/** + * @brief Start measurement mode + * @note Not used in admin comands, getMetrics or init, can change clock. + */ +uint32_t SCD30Sensor::wakeUp() +{ + +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return 0; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + startMeasurement(); + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + return 0; +} + +/** + * @brief Stop measurement mode + * @note Not used in admin comands, getMetrics or init, can change clock. + */ +void SCD30Sensor::sleep() +{ +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + stopMeasurement(); + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif +} + +bool SCD30Sensor::canSleep() +{ + return false; +} + +int32_t SCD30Sensor::wakeUpTimeMs() +{ + return 0; +} + +int32_t SCD30Sensor::pendingForReadyMs() +{ + return 0; +} + +AdminMessageHandleResult SCD30Sensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) +{ + AdminMessageHandleResult result; + +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return AdminMessageHandleResult::NOT_HANDLED; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + switch (request->which_payload_variant) { + case meshtastic_AdminMessage_sensor_config_tag: + // Check for ASC-FRC request first + if (!request->sensor_config.has_scd30_config) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } + + if (request->sensor_config.scd30_config.has_soft_reset) { + LOG_DEBUG("%s: Requested soft reset", sensorName); + this->softReset(); + } else { + + if (request->sensor_config.scd30_config.has_set_asc) { + this->setASC(request->sensor_config.scd30_config.set_asc); + if (request->sensor_config.scd30_config.set_asc == false) { + LOG_DEBUG("%s: Request for FRC", sensorName); + if (request->sensor_config.scd30_config.has_set_target_co2_conc) { + this->performFRC(request->sensor_config.scd30_config.set_target_co2_conc); + } else { + // FRC requested but no target CO2 provided + LOG_ERROR("%s: target CO2 not provided", sensorName); + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } + } + } + + // Check for temperature offset + // NOTE: this requires to have a sensor working on stable environment + // And to make it between readings + if (request->sensor_config.scd30_config.has_set_temperature) { + this->setTemperature(request->sensor_config.scd30_config.set_temperature); + } + + // Check for altitude + if (request->sensor_config.scd30_config.has_set_altitude) { + this->setAltitude(request->sensor_config.scd30_config.set_altitude); + } + + // Check for set measuremen interval + if (request->sensor_config.scd30_config.has_set_measurement_interval) { + this->setMeasurementInterval(request->sensor_config.scd30_config.set_measurement_interval); + } + } + + result = AdminMessageHandleResult::HANDLED; + break; + + default: + result = AdminMessageHandleResult::NOT_HANDLED; + } + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + return result; +} + +#endif diff --git a/src/modules/Telemetry/Sensor/SCD30Sensor.h b/src/modules/Telemetry/Sensor/SCD30Sensor.h new file mode 100644 index 000000000..6e03e2dda --- /dev/null +++ b/src/modules/Telemetry/Sensor/SCD30Sensor.h @@ -0,0 +1,53 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +#define SCD30_I2C_CLOCK_SPEED 100000 + +class SCD30Sensor : public TelemetrySensor +{ + private: + SensirionI2cScd30 scd30; + TwoWire *_bus{}; + uint8_t _address{}; + + bool performFRC(uint16_t targetCO2); + bool setASC(bool ascEnabled); + bool getASC(uint16_t &ascEnabled); + bool setTemperature(float tempReference); + bool getAltitude(uint16_t &altitude); + bool setAltitude(uint16_t altitude); + bool softReset(); // + bool setMeasurementInterval(uint16_t measInterval); + bool getMeasurementInterval(uint16_t &measInterval); + bool startMeasurement(); + bool stopMeasurement(); + + // Parameters + uint16_t ascActive = 1; + uint16_t measurementInterval = 2; + + public: + SCD30Sensor(); + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + + enum SCD30State { SCD30_OFF, SCD30_IDLE, SCD30_MEASUREMENT }; + SCD30State state = SCD30_OFF; + + virtual bool isActive() override; + + virtual void sleep() override; // Stops measurement (measurement -> idle) + virtual uint32_t wakeUp() override; // Starts measurement (idle -> measurement) + virtual bool canSleep() override; + virtual int32_t wakeUpTimeMs() override; + virtual int32_t pendingForReadyMs() override; + AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) override; +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp index 4113de315..c6ab7bb04 100644 --- a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp @@ -217,7 +217,7 @@ bool SCD4XSensor::startMeasurement() state = SCD4X_MEASUREMENT; return true; } else { - LOG_ERROR("%s: Couldn't start measurement mode", sensorName); + LOG_ERROR("%s: Unable to start measurement mode", sensorName); return false; } } @@ -232,7 +232,7 @@ bool SCD4XSensor::stopMeasurement() error = scd4x.stopPeriodicMeasurement(); if (error != SCD4X_NO_ERROR) { - LOG_ERROR("%s: Unable to set idle mode on SCD4X.", sensorName); + LOG_ERROR("%s: Unable to stop measurement.", sensorName); return false; } @@ -283,11 +283,7 @@ bool SCD4XSensor::getASC(uint16_t &_ascActive) return false; } - if (_ascActive) { - LOG_INFO("%s: ASC is enabled", sensorName); - } else { - LOG_INFO("%s: FRC is enabled", sensorName); - } + LOG_INFO("%s ASC is %s", sensorName, _ascActive ? "enabled" : "disabled"); return true; } @@ -305,11 +301,7 @@ bool SCD4XSensor::setASC(bool ascEnabled) { uint16_t error; - if (ascEnabled) { - LOG_INFO("%s: Enabling ASC", sensorName); - } else { - LOG_INFO("%s: Disabling ASC", sensorName); - } + LOG_INFO("%s %s ASC", sensorName, ascEnabled ? "Enabling" : "Disabling"); if (!stopMeasurement()) { return false; @@ -351,7 +343,6 @@ bool SCD4XSensor::setASC(bool ascEnabled) */ bool SCD4XSensor::setASCBaseline(uint32_t targetCO2) { - // TODO - Remove? // Available in library, but not described in datasheet. uint16_t error; LOG_INFO("%s: Setting ASC baseline to: %u", sensorName, targetCO2); diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp index 49d5a5f94..b0f0f9071 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -205,7 +205,6 @@ uint8_t SEN5XSensor::sen5xCRC(const uint8_t *buffer) void SEN5XSensor::sleep() { - // TODO Check this works idle(true); } @@ -230,41 +229,43 @@ bool SEN5XSensor::idle(bool checkState) // Check if we have time, and store it uint32_t now; // If time is RTCQualityNone, it will return zero now = getValidTime(RTCQuality::RTCQualityDevice); + // Check if state is valid (non-zero) if (now) { - // Check if state is valid (non-zero) vocTime = now; } } - if (vocStateStable() && vocValid) { - saveState(); - } else { - LOG_INFO("SEN5X: Not stopping measurement, vocState is not stable yet!"); + if (!(vocStateStable() && vocValid)) { + LOG_INFO("%s: Not stopping measurement, vocState is not stable yet!", sensorName); return true; } } + // Save state and prefs (on all models) + saveState(); } if (!oneShotMode) { - LOG_INFO("SEN5X: Not stopping measurement, continuous mode!"); + LOG_INFO("%s: Not stopping measurement, continuous mode!", sensorName); return true; + } else { + LOG_INFO("%s: One shot mode enabled", sensorName); } // Switch to low-power based on the model if (model == SEN50) { if (!sendCommand(SEN5X_STOP_MEASUREMENT)) { - LOG_ERROR("SEN5X: Error stopping measurement"); + LOG_ERROR("%s: Error stopping measurement", sensorName); return false; } state = SEN5X_IDLE; - LOG_INFO("SEN5X: Stop measurement mode"); + LOG_INFO("%s: Stop measurement mode", sensorName); } else { if (!sendCommand(SEN5X_START_MEASUREMENT_RHT_GAS)) { - LOG_ERROR("SEN5X: Error switching to RHT/Gas measurement"); + LOG_ERROR("%s: Error switching to RHT/Gas measurement", sensorName); return false; } state = SEN5X_RHTGAS_ONLY; - LOG_INFO("SEN5X: Switch to RHT/Gas only measurement mode"); + LOG_INFO("%s: Switch to RHT/Gas only measurement mode", sensorName); } delay(200); // From Sensirion Datasheet @@ -289,10 +290,10 @@ bool SEN5XSensor::vocStateValid() { if (!vocState[0] && !vocState[1] && !vocState[2] && !vocState[3] && !vocState[4] && !vocState[5] && !vocState[6] && !vocState[7]) { - LOG_DEBUG("SEN5X: VOC state is all 0, invalid"); + LOG_DEBUG("%s: VOC state is all 0, invalid", sensorName); return false; } else { - LOG_DEBUG("SEN5X: VOC state is valid"); + LOG_DEBUG("%s: VOC state is valid", sensorName); return true; } } @@ -618,7 +619,7 @@ bool SEN5XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) } } else { // TODO - Should this actually ignore? We could end up never cleaning... - LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved state. Trying again later"); + LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved cleaning and VOC state"); } idle(false); @@ -965,4 +966,4 @@ AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPa return result; } -#endif \ No newline at end of file +#endif From 321ccbf5e2418f70cf1a9316bbce4ad20221e656 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 05:52:46 -0600 Subject: [PATCH 159/223] Upgrade trunk (#9661) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 03a60cad0..40048ea7b 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,7 +9,7 @@ plugins: lint: enabled: - checkov@3.2.501 - - renovate@43.10.3 + - renovate@43.15.3 - prettier@3.8.1 - trufflehog@3.93.3 - yamllint@1.38.0 @@ -21,7 +21,7 @@ lint: - markdownlint@0.47.0 - oxipng@10.1.0 - svgo@4.0.0 - - actionlint@1.7.10 + - actionlint@1.7.11 - flake8@7.3.0 - hadolint@2.14.0 - shfmt@3.6.0 From 0cbf53b7a72c4d475c54e95e0e20425e183616ef Mon Sep 17 00:00:00 2001 From: Wessel Date: Mon, 16 Feb 2026 13:49:58 +0100 Subject: [PATCH 160/223] fix: respect DontMqttMeBro flag regardless of channel PSK (#9626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous PSK check was broken from its introduction in #4643 — memcmp was used in boolean context without comparing to 0, inverting the condition. Since no one noticed for over a year, the PSK-based filtering provided no practical value. Simplifying to always respect the sender's preference is both more correct and easier to reason about. --- src/mqtt/MQTT.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 18a4f913e..2c10c4b2b 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -754,10 +754,8 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { // For uplinking other's packets, check if it's not OK to MQTT or if it's an older packet without the bitfield bool dontUplink = !mp_decoded.decoded.has_bitfield || !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK); - // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. - if (!isFromUs(&mp_decoded) && !isMqttServerAddressPrivate && dontUplink && - (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || - (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) { + // Respect the DontMqttMeBro flag for other nodes' packets on public MQTT servers + if (!isFromUs(&mp_decoded) && !isMqttServerAddressPrivate && dontUplink) { LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag"); return; } From 5feba46b53e006a42a2c3d4091c3f35d9132b601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 16 Feb 2026 16:39:31 +0100 Subject: [PATCH 161/223] our firmware action is too clever Update pio_target and add pio_opts for checks. --- .github/workflows/main_matrix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 49379fbd1..876d7e4ce 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -95,7 +95,8 @@ jobs: with: pio_platform: ${{ matrix.check.platform }} pio_env: ${{ matrix.check.board }} - pio_target: check --fail-on-defect=low + pio_target: check + pio_opts: --fail-on-defect=low build: needs: [setup, version] From 6b44b5786e056a121f5e96edf1fbc6b940ce69b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 16 Feb 2026 17:07:07 +0100 Subject: [PATCH 162/223] fix detection of SCD30 by checking if the size of the return from a 2 byte register read is correct (#9664) * fix detection of SCD30 by checking if thee size of the return from a 2 byte register read is correct fix signedness warning in PMSA003 sensor code. * Add alternate path for LPS22HB Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * check EndTransmission for errors and compare returned length to expected value Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/detect/ScanI2CTwoWire.cpp | 34 ++++++++++++++++--- src/detect/ScanI2CTwoWire.h | 2 ++ .../Telemetry/Sensor/PMSA003ISensor.cpp | 2 +- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index df0fad792..c58a58518 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -117,6 +117,25 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation return value; } +bool ScanI2CTwoWire::i2cCommandResponseLength(ScanI2C::DeviceAddress addr, uint16_t command, uint8_t expectedLength) const +{ + TwoWire *i2cBus = fetchI2CBus(addr); + i2cBus->beginTransmission(addr.address); + if (command > 0xFF) { + i2cBus->write((uint8_t)(command >> 8)); + } + i2cBus->write((uint8_t)(command & 0xFF)); + if (i2cBus->endTransmission() != 0) { + return false; + } + delay(20); + uint8_t received = i2cBus->requestFrom(addr.address, expectedLength); + bool match = (received == expectedLength); + while (i2cBus->available()) + i2cBus->read(); + return match; +} + /// for SEN5X detection // Note, this code needs to be called before setting the I2C bus speed // for the screen at high speed. The speed needs to be at 100kHz, otherwise @@ -432,8 +451,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { type = OPT3001; logFoundDevice("OPT3001", (uint8_t)addr.address); - } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 6) != - 0) { // unique SHT4x serial number (6 bytes inc. CRC) + } else if (i2cCommandResponseLength(addr, 0x89, 6)) { // SHT4x serial number (6 bytes inc. CRC) type = SHT4X; logFoundDevice("SHT4X", (uint8_t)addr.address); } else { @@ -458,13 +476,19 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; case LPS22HB_ADDR_ALT: - registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xD060), 48); // get device marking - if (registerValue != 0) { + // SFA30 detection: send 2-byte command 0xD060 (Get Device Marking) and check for 48-byte response + if (i2cCommandResponseLength(addr, 0xD060, 48)) { type = SFA30; logFoundDevice("SFA30", (uint8_t)addr.address); break; } - // TODO - What happens with these two? + // Fallback: LPS22HB detection at alternate address using WHO_AM_I register (0x0F == 0xB1) + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); + if (registerValue == 0xB1) { + type = LPS22HB; + logFoundDevice("LPS22HB", (uint8_t)addr.address); + } + break; SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address) SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address) diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index c5b791920..841a8b946 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -55,6 +55,8 @@ class ScanI2CTwoWire : public ScanI2C uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth, bool) const; + bool i2cCommandResponseLength(DeviceAddress addr, uint16_t command, uint8_t expectedLength) const; + DeviceType probeOLED(ScanI2C::DeviceAddress) const; static void logFoundDevice(const char *device, uint8_t address); diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index 069931e21..e34b70a1f 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -67,7 +67,7 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) #endif /* CAN_RECLOCK_I2C */ #endif /* PMSA003I_I2C_CLOCK_SPEED */ - _bus->requestFrom(_address, PMSA003I_FRAME_LENGTH); + _bus->requestFrom(_address, (uint8_t)PMSA003I_FRAME_LENGTH); if (_bus->available() < PMSA003I_FRAME_LENGTH) { LOG_WARN("%s read failed: incomplete data (%d bytes)", sensorName, _bus->available()); return false; From a227fd70289764f6499833aa27258a7f25c4a29f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 16 Feb 2026 19:39:43 +0100 Subject: [PATCH 163/223] #9623 resolved a local shadow of next_key by converting it to int. (#9665) --- src/input/HackadayCommunicatorKeyboard.cpp | 9 ++++----- src/input/HackadayCommunicatorKeyboard.h | 4 ++-- src/input/MPR121Keyboard.cpp | 6 +++--- src/input/MPR121Keyboard.h | 2 +- src/input/TCA8418Keyboard.cpp | 10 +++++----- src/input/TCA8418Keyboard.h | 4 ++-- src/input/TDeckProKeyboard.cpp | 9 ++++----- src/input/TDeckProKeyboard.h | 4 ++-- src/input/TLoraPagerKeyboard.cpp | 9 ++++----- src/input/TLoraPagerKeyboard.h | 4 ++-- 10 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/input/HackadayCommunicatorKeyboard.cpp b/src/input/HackadayCommunicatorKeyboard.cpp index c6a9e0ae8..b096c74d2 100644 --- a/src/input/HackadayCommunicatorKeyboard.cpp +++ b/src/input/HackadayCommunicatorKeyboard.cpp @@ -106,8 +106,8 @@ static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{}, {}}; HackadayCommunicatorKeyboard::HackadayCommunicatorKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), - last_tap(0L), char_idx(0), tap_interval(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX), + next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0) { reset(); } @@ -147,7 +147,6 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key) modifierFlag = 0; } - uint8_t next_key = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { @@ -187,8 +186,8 @@ void HackadayCommunicatorKeyboard::released() return; } - if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { - last_key = -1; + if (last_key >= _TCA8418_NUM_KEYS) { + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/HackadayCommunicatorKeyboard.h b/src/input/HackadayCommunicatorKeyboard.h index 8316bed72..cbba5c12f 100644 --- a/src/input/HackadayCommunicatorKeyboard.h +++ b/src/input/HackadayCommunicatorKeyboard.h @@ -18,8 +18,8 @@ class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed uint32_t last_modifier_time; // Timestamp of the last modifier key press - int8_t last_key; - int8_t next_key; + uint8_t last_key; + uint8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; diff --git a/src/input/MPR121Keyboard.cpp b/src/input/MPR121Keyboard.cpp index 9bca6801d..ec37cfbaa 100644 --- a/src/input/MPR121Keyboard.cpp +++ b/src/input/MPR121Keyboard.cpp @@ -91,7 +91,7 @@ MPR121Keyboard::MPR121Keyboard() : m_wire(nullptr), m_addr(0), readCallback(null { // LOG_DEBUG("MPR121 @ %02x", m_addr); state = Init; - last_key = -1; + last_key = UINT8_MAX; last_tap = 0L; char_idx = 0; queue = ""; @@ -359,8 +359,8 @@ void MPR121Keyboard::released() return; } // would clear longpress callback... later. - if (last_key < 0 || last_key > _NUM_KEYS) { // reset to idle if last_key out of bounds - last_key = -1; + if (last_key >= _NUM_KEYS) { // reset to idle if last_key out of bounds + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/MPR121Keyboard.h b/src/input/MPR121Keyboard.h index 6349750ce..ec3f56e87 100644 --- a/src/input/MPR121Keyboard.h +++ b/src/input/MPR121Keyboard.h @@ -14,7 +14,7 @@ class MPR121Keyboard MPR121States state; - int8_t last_key; + uint8_t last_key; uint32_t last_tap; uint8_t char_idx; diff --git a/src/input/TCA8418Keyboard.cpp b/src/input/TCA8418Keyboard.cpp index e0321b462..822885a9f 100644 --- a/src/input/TCA8418Keyboard.cpp +++ b/src/input/TCA8418Keyboard.cpp @@ -43,8 +43,8 @@ static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = { }; TCA8418Keyboard::TCA8418Keyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(-1), next_key(-1), last_tap(0L), char_idx(0), tap_interval(0), - should_backspace(false) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(UINT8_MAX), next_key(UINT8_MAX), last_tap(0L), char_idx(0), + tap_interval(0), should_backspace(false) { } @@ -71,7 +71,7 @@ void TCA8418Keyboard::pressed(uint8_t key) } // Compute key index based on dynamic row/column - next_key = (int8_t)(row * _TCA8418_COLS + col); + next_key = (uint8_t)(row * _TCA8418_COLS + col); // LOG_DEBUG("TCA8418: Key %u -> Next Key %u", key, next_key); @@ -105,8 +105,8 @@ void TCA8418Keyboard::released() return; } - if (last_key < 0 || last_key > _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds - last_key = -1; + if (last_key >= _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/TCA8418Keyboard.h b/src/input/TCA8418Keyboard.h index b76916643..0e8821260 100644 --- a/src/input/TCA8418Keyboard.h +++ b/src/input/TCA8418Keyboard.h @@ -14,8 +14,8 @@ class TCA8418Keyboard : public TCA8418KeyboardBase void pressed(uint8_t key) override; void released(void) override; - int8_t last_key; - int8_t next_key; + uint8_t last_key; + uint8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp index eeafe4949..b83f0c6ae 100644 --- a/src/input/TDeckProKeyboard.cpp +++ b/src/input/TDeckProKeyboard.cpp @@ -62,8 +62,8 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { }; TDeckProKeyboard::TDeckProKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), - last_tap(0L), char_idx(0), tap_interval(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX), + next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0) { } @@ -101,7 +101,6 @@ void TDeckProKeyboard::pressed(uint8_t key) modifierFlag = 0; } - uint8_t next_key = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; @@ -142,8 +141,8 @@ void TDeckProKeyboard::released() return; } - if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { - last_key = -1; + if (last_key >= _TCA8418_NUM_KEYS) { + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/TDeckProKeyboard.h b/src/input/TDeckProKeyboard.h index 617f3f20b..3ef97fc3d 100644 --- a/src/input/TDeckProKeyboard.h +++ b/src/input/TDeckProKeyboard.h @@ -19,8 +19,8 @@ class TDeckProKeyboard : public TCA8418KeyboardBase private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed uint32_t last_modifier_time; // Timestamp of the last modifier key press - int8_t last_key; - int8_t next_key; + uint8_t last_key; + uint8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp index 9a4fd8679..4efa5d6e2 100644 --- a/src/input/TLoraPagerKeyboard.cpp +++ b/src/input/TLoraPagerKeyboard.cpp @@ -65,8 +65,8 @@ static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'}, {' ', 0x00, Key::BL_TOGGLE}}; TLoraPagerKeyboard::TLoraPagerKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), - last_tap(0L), char_idx(0), tap_interval(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX), + next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); @@ -129,7 +129,6 @@ void TLoraPagerKeyboard::pressed(uint8_t key) modifierFlag = 0; } - uint8_t next_key = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; @@ -170,8 +169,8 @@ void TLoraPagerKeyboard::released() return; } - if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { - last_key = -1; + if (last_key >= _TCA8418_NUM_KEYS) { + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h index f04d2ce6a..06a4c7b63 100644 --- a/src/input/TLoraPagerKeyboard.h +++ b/src/input/TLoraPagerKeyboard.h @@ -21,8 +21,8 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed uint32_t last_modifier_time; // Timestamp of the last modifier key press - int8_t last_key; - int8_t next_key; + uint8_t last_key; + uint8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; From a4ba3125792b067315307b1ea48b2904367831a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 17 Feb 2026 09:53:20 +0100 Subject: [PATCH 164/223] zip a few gitrefs down (#9672) --- src/graphics/niche/InkHUD/PlatformioConfig.ini | 4 ++-- variants/esp32s3/unphone/platformio.ini | 4 ++-- variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/graphics/niche/InkHUD/PlatformioConfig.ini b/src/graphics/niche/InkHUD/PlatformioConfig.ini index b985f9f77..67ad5098f 100644 --- a/src/graphics/niche/InkHUD/PlatformioConfig.ini +++ b/src/graphics/niche/InkHUD/PlatformioConfig.ini @@ -8,5 +8,5 @@ build_flags = -D MESHTASTIC_EXCLUDE_INPUTBROKER ; Suppress default input handling -D HAS_BUTTON=0 ; Suppress default ButtonThread lib_deps = - # TODO renovate - https://github.com/ZinggJM/GFX_Root#2.0.0 ; Used by InkHUD as a "slimmer" version of AdafruitGFX + # renovate: datasource=github-tags depName=GFX_Root packageName=ZinggJM/GFX_Root + https://github.com/ZinggJM/GFX_Root/archive/3195764e352a0d2567c8d277ac408ca7293a99b0.zip ; Used by InkHUD as a "slimmer" version of AdafruitGFX diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index f52fcc09a..1d4af52f3 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -38,8 +38,8 @@ build_src_filter = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 - # TODO renovate - https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 + # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 + https://gitlab.com/hamishcunningham/unphonelibrary/-/archive/meshtastic/unphonelibrary-meshtastic.zip # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.4 diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini index 07d763df3..9b15e668a 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini @@ -24,8 +24,8 @@ build_flags = ${nrf52840_base.build_flags} build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631_nomadstar_meteor_pro> + + lib_deps = ${nrf52840_base.lib_deps} - # TODO renovate - https://github.com/NomadStar-outdoor/IOBoard-RGB-LP5562-Library.git#9c366c8 + # renovate: datasource=git-refs depName=IOBoard-RGB-LP5562-Library packageName=NomadStar-outdoor/IOBoard-RGB-LP5562-Library gitBranch=master + https://github.com/NomadStar-outdoor/IOBoard-RGB-LP5562-Library/archive/9c366c875e1e8103ed97b5d4c09f3878345da80a.zip ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds From 3ca68e7788d0ee808de089955f268b5051187994 Mon Sep 17 00:00:00 2001 From: Vortetty <33466216+Vortetty@users.noreply.github.com> Date: Tue, 17 Feb 2026 02:03:26 -0800 Subject: [PATCH 165/223] InkHUD: Allow non-system applets to subscribe to input events (#9514) * Allow inkhud user applets to consume inputs with opt-in system Adds a way for applets to subscribe to input events while keeping it off by default to preserve compatibility and expected behaviours. Adds example for use as well. * Add check for nullptr on getActiveApplet uses * Remove redundant includes * Move subscribedInputs to protected * More consistent naming scheme --- src/graphics/niche/InkHUD/Applet.cpp | 15 ++++ src/graphics/niche/InkHUD/Applet.h | 22 ++++++ .../UserAppletInputExample.cpp | 79 +++++++++++++++++++ .../UserAppletInputExample.h | 36 +++++++++ src/graphics/niche/InkHUD/Events.cpp | 73 ++++++++++++++--- src/graphics/niche/InkHUD/InkHUD.cpp | 6 ++ src/graphics/niche/InkHUD/InkHUD.h | 1 + src/graphics/niche/InkHUD/WindowManager.cpp | 6 ++ src/graphics/niche/InkHUD/WindowManager.h | 1 + .../seeed_wio_tracker_L1_eink/nicheGraphics.h | 1 + 10 files changed, 228 insertions(+), 12 deletions(-) create mode 100644 src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp create mode 100644 src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index 1468bb23d..0a9cd3add 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -144,6 +144,21 @@ void InkHUD::Applet::resetDrawingSpace() setFont(fontSmall); } +// Sets one or more inputs to enabled/disabled for this applet and if they should be sent to it +void InkHUD::Applet::setInputsSubscribed(uint8_t input, bool captured) +{ + if (captured) + subscribedInputs |= input; + else + subscribedInputs &= ~input; +} + +// Checks if a specific input is enabled for this applet and should be sent to it +bool InkHUD::Applet::isInputSubscribed(InputMask input) +{ + return (subscribedInputs & input) == input; +} + // Tell InkHUD::Renderer that we want to render now // Applets should internally listen for events they are interested in, via MeshModule, CallbackObserver etc // When an applet decides it has heard something important, and wants to redraw, it calls this method diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index 2a349052d..84fd86465 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -89,6 +89,9 @@ class Applet : public GFX virtual void onForeground() {} virtual void onBackground() {} virtual void onShutdown() {} + + // Input Events + virtual void onButtonShortPress() {} virtual void onButtonLongPress() {} virtual void onExitShort() {} @@ -100,6 +103,18 @@ class Applet : public GFX virtual void onFreeText(char c) {} virtual void onFreeTextDone() {} virtual void onFreeTextCancel() {} + // List of inputs which can be subscribed to + enum InputMask { // | No Joystick | With Joystick | + BUTTON_SHORT = 1, // | Button Click | Joystick Center Click | + BUTTON_LONG = 2, // | Button Hold | Joystick Center Hold | + EXIT_SHORT = 4, // | no-op | Back Button Click | + EXIT_LONG = 8, // | no-op | Back Button Hold | + NAV_UP = 16, // | no-op | Joystick Up | + NAV_DOWN = 32, // | no-op | Joystick Down | + NAV_LEFT = 64, // | no-op | Joystick Left | + NAV_RIGHT = 128 // | no-op | Joystick Right | + }; + bool isInputSubscribed(InputMask input); // Check if input should be handled by applet, this should not be overloaded. virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification @@ -121,6 +136,13 @@ class Applet : public GFX void setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region void resetCrop(); // Removes setCrop() + // User Input Handling + + uint8_t subscribedInputs = 0b00000000; // Maybe uint16_t for futureproofing? other devices may need more inputs + void setInputsSubscribed(uint8_t input, + bool captured); // Set if an input should be handled by applet or not, this should not be + // overloaded. Can take multiple inputs at once if you OR/`|` them together + // Text void setFont(AppletFont f); diff --git a/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp b/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp new file mode 100644 index 000000000..79133719a --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp @@ -0,0 +1,79 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD +#include "./UserAppletInputExample.h" + +using namespace NicheGraphics; + +void InkHUD::UserAppletInputExampleApplet::onActivate() +{ + setGrabbed(false); +} + +void InkHUD::UserAppletInputExampleApplet::onRender(bool full) +{ + drawHeader("Input Example"); + uint16_t headerHeight = getHeaderHeight(); + + std::string buttonName; + if (settings->joystick.enabled) + buttonName = "joystick center button"; + else + buttonName = "user button"; + + std::string additional = " | Control is grabbed, long press " + buttonName + " to release controls"; + if (!isGrabbed) + additional = " | Control is released, long press " + buttonName + " to grab controls"; + + printWrapped(0, headerHeight, width(), "Last button: " + lastInput + additional); +} + +void InkHUD::UserAppletInputExampleApplet::setGrabbed(bool grabbed) +{ + isGrabbed = grabbed; + setInputsSubscribed(BUTTON_SHORT | EXIT_SHORT | EXIT_LONG | NAV_UP | NAV_DOWN | NAV_LEFT | NAV_RIGHT, + grabbed); // Enables/disables grabbing all inputs + setInputsSubscribed(BUTTON_LONG, true); // Always grab this input +} + +void InkHUD::UserAppletInputExampleApplet::onButtonShortPress() +{ + lastInput = "BUTTON_SHORT"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onButtonLongPress() +{ + lastInput = "BUTTON_LONG"; + setGrabbed(!isGrabbed); + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onExitShort() +{ + lastInput = "EXIT_SHORT"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onExitLong() +{ + lastInput = "EXIT_LONG"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onNavUp() +{ + lastInput = "NAV_UP"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onNavDown() +{ + lastInput = "NAV_DOWN"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onNavLeft() +{ + lastInput = "NAV_LEFT"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onNavRight() +{ + lastInput = "NAV_RIGHT"; + requestUpdate(); +} + +#endif \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h b/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h new file mode 100644 index 000000000..a99dec00c --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h @@ -0,0 +1,36 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +#pragma once + +#include "configuration.h" + +#include "graphics/niche/InkHUD/Applet.h" + +namespace NicheGraphics::InkHUD +{ + +class UserAppletInputExampleApplet : public Applet +{ + public: + void onActivate() override; + + void onRender(bool full) override; + void onButtonShortPress() override; + void onButtonLongPress() override; + void onExitShort() override; + void onExitLong() override; + void onNavUp() override; + void onNavDown() override; + void onNavLeft() override; + void onNavRight() override; + + private: + std::string lastInput = "None"; + bool isGrabbed = false; + + void setGrabbed(bool grabbed); +}; + +} // namespace NicheGraphics::InkHUD + +#endif \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index e6c16d350..577a773bb 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -59,10 +59,16 @@ void InkHUD::Events::onButtonShort() if (consumer) { consumer->onButtonShortPress(); } else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module - if (!settings->joystick.enabled) - inkhud->nextApplet(); - else - inkhud->openMenu(); + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::BUTTON_SHORT)) + userConsumer->onButtonShortPress(); + else { + if (!settings->joystick.enabled) + inkhud->nextApplet(); + else + inkhud->openMenu(); + } } } @@ -84,8 +90,14 @@ void InkHUD::Events::onButtonLong() // If no system applet is handling input, default behavior instead is to open the menu if (consumer) consumer->onButtonLongPress(); - else - inkhud->openMenu(); + else { + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::BUTTON_LONG)) + userConsumer->onButtonLongPress(); + else + inkhud->openMenu(); + } } void InkHUD::Events::onExitShort() @@ -110,8 +122,14 @@ void InkHUD::Events::onExitShort() // If no system applet is handling input, default behavior instead is change tiles if (consumer) consumer->onExitShort(); - else if (!dismissedExt) // Don't change tile if this button press silenced the external notification module - inkhud->nextTile(); + else if (!dismissedExt) { // Don't change tile if this button press silenced the external notification module + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_SHORT)) + userConsumer->onExitShort(); + else + inkhud->nextTile(); + } } } @@ -133,6 +151,13 @@ void InkHUD::Events::onExitLong() if (consumer) consumer->onExitLong(); + else { + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_LONG)) + userConsumer->onExitLong(); + // Nothing uses exit long yet + } } } @@ -157,6 +182,12 @@ void InkHUD::Events::onNavUp() if (consumer) consumer->onNavUp(); + else if (!dismissedExt) { + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_UP)) + userConsumer->onNavUp(); + } } } @@ -181,6 +212,12 @@ void InkHUD::Events::onNavDown() if (consumer) consumer->onNavDown(); + else if (!dismissedExt) { + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_DOWN)) + userConsumer->onNavDown(); + } } } @@ -206,8 +243,14 @@ void InkHUD::Events::onNavLeft() // If no system applet is handling input, default behavior instead is to cycle applets if (consumer) consumer->onNavLeft(); - else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module - inkhud->prevApplet(); + else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_LEFT)) + userConsumer->onNavLeft(); + else + inkhud->prevApplet(); + } } } @@ -233,8 +276,14 @@ void InkHUD::Events::onNavRight() // If no system applet is handling input, default behavior instead is to cycle applets if (consumer) consumer->onNavRight(); - else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module - inkhud->nextApplet(); + else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_RIGHT)) + userConsumer->onNavRight(); + else + inkhud->nextApplet(); + } } } diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD/InkHUD.cpp index 5fab67639..edffda6b7 100644 --- a/src/graphics/niche/InkHUD/InkHUD.cpp +++ b/src/graphics/niche/InkHUD/InkHUD.cpp @@ -210,6 +210,12 @@ void InkHUD::InkHUD::prevApplet() windowManager->prevApplet(); } +// Returns the currently active applet +InkHUD::Applet *InkHUD::InkHUD::getActiveApplet() +{ + return windowManager->getActiveApplet(); +} + // Show the menu (on the the focused tile) // The applet previously displayed there will be restored once the menu closes void InkHUD::InkHUD::openMenu() diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD/InkHUD.h index ae029137e..0e25b0900 100644 --- a/src/graphics/niche/InkHUD/InkHUD.h +++ b/src/graphics/niche/InkHUD/InkHUD.h @@ -74,6 +74,7 @@ class InkHUD void nextApplet(); void prevApplet(); + NicheGraphics::InkHUD::Applet *getActiveApplet(); void openMenu(); void openAlignStick(); void openKeyboard(); diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index a80b468d5..fce3c9770 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -273,6 +273,12 @@ void InkHUD::WindowManager::prevApplet() inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST } +// Returns active applet +NicheGraphics::InkHUD::Applet *InkHUD::WindowManager::getActiveApplet() +{ + return userTiles.at(settings->userTiles.focused)->getAssignedApplet(); +} + // Rotate the display image by 90 degrees void InkHUD::WindowManager::rotate() { diff --git a/src/graphics/niche/InkHUD/WindowManager.h b/src/graphics/niche/InkHUD/WindowManager.h index 948ef6131..a11688cf5 100644 --- a/src/graphics/niche/InkHUD/WindowManager.h +++ b/src/graphics/niche/InkHUD/WindowManager.h @@ -29,6 +29,7 @@ class WindowManager void nextTile(); void prevTile(); + Applet *getActiveApplet(); void openMenu(); void openAlignStick(); void openKeyboard(); diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h index 9c9c788c7..2a2967f5e 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h @@ -9,6 +9,7 @@ #include "graphics/niche/InkHUD/InkHUD.h" // Applets +#include "graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h" #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" #include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" From f94c01b452ebea59d2b802c9099d744285899369 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 05:38:13 -0600 Subject: [PATCH 166/223] Upgrade trunk (#9671) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 40048ea7b..215b31a02 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,8 +8,8 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.501 - - renovate@43.15.3 + - checkov@3.2.502 + - renovate@43.19.2 - prettier@3.8.1 - trufflehog@3.93.3 - yamllint@1.38.0 From 4fccda26c66b980bba43715fb92fffbd6cbe2492 Mon Sep 17 00:00:00 2001 From: harry-iii-lord <256662038+harry-iii-lord@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:44:26 +0100 Subject: [PATCH 167/223] Concurrency: Modern Periodic wrapper class. (#9501) --- src/concurrency/Periodic.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/concurrency/Periodic.h b/src/concurrency/Periodic.h index db07145a6..dc20e6d56 100644 --- a/src/concurrency/Periodic.h +++ b/src/concurrency/Periodic.h @@ -1,24 +1,29 @@ #pragma once +#include +#include + #include "concurrency/OSThread.h" namespace concurrency { /** - * @brief Periodically invoke a callback. This just provides C-style callback conventions - * rather than a virtual function - FIXME, remove? + * @brief Periodically invoke a callback. + * Supports both legacy function pointers and modern callables. */ class Periodic : public OSThread { - int32_t (*callback)(); - public: // callback returns the period for the next callback invocation (or 0 if we should no longer be called) - Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {} + Periodic(const char *name, int32_t (*cb)()) : OSThread(name), callback(cb) {} + Periodic(const char *name, std::function cb) : OSThread(name), callback(std::move(cb)) {} protected: - int32_t runOnce() override { return callback(); } + int32_t runOnce() override { return callback ? callback() : 0; } + + private: + std::function callback; }; } // namespace concurrency From 357a33f4542e8fe81f5953bc47ab96762b5e757d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 05:45:54 -0600 Subject: [PATCH 168/223] chore(deps): update actions/stale action to v10.2.0 (#9669) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/stale_bot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml index fc0702bd8..9255975a8 100644 --- a/.github/workflows/stale_bot.yml +++ b/.github/workflows/stale_bot.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Stale PR+Issues - uses: actions/stale@v10.1.1 + uses: actions/stale@v10.2.0 with: days-before-stale: 45 stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days. From 178c3114d90a9058c0d286b0e8af7d343504a02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 17 Feb 2026 22:16:56 +0100 Subject: [PATCH 169/223] Fake IAQ values on Non-BSEC2 platforms like Platformio and the original ESP32 (#9663) * BSEC2 Replacement - add approximation for IAQ to non-BSEC2 platforms. - Re-add this sensor to ESP32 targets, and refactor env_extra includes. - Fix C++ 11 compatibility * Check for gas resistance 0 --- platformio.ini | 50 ++++++------------- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 26 +++++++++- src/modules/Telemetry/Sensor/BME680Sensor.h | 2 +- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1d18b4573..ccc5c9755 100644 --- a/platformio.ini +++ b/platformio.ini @@ -184,8 +184,8 @@ lib_deps = # renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE wollewald/BH1750_WE@1.1.10 -; (not included in native / portduino) -[environmental_extra] +; Common environmental sensor libraries (not included in native / portduino) +[environmental_extra_common] lib_deps = # renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library adafruit/Adafruit BMP3XX Library@2.1.6 @@ -205,10 +205,6 @@ lib_deps = sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6 # renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001 closedcube/ClosedCube OPT3001@1.1.2 - # renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2 - boschsensortec/bsec2@1.10.2610 - # renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library - boschsensortec/BME68x Sensor Library@1.3.40408 # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core @@ -220,34 +216,18 @@ lib_deps = # renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30 sensirion/Sensirion I2C SCD30@1.0.0 -; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) +; Environmental sensors with BSEC2 (Bosch proprietary IAQ) +[environmental_extra] +lib_deps = + ${environmental_extra_common.lib_deps} + # renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2 + boschsensortec/bsec2@1.10.2610 + # renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library + boschsensortec/BME68x Sensor Library@1.3.40408 + +; Environmental sensors without BSEC (saves ~3.5KB DRAM for original ESP32 targets) [environmental_extra_no_bsec] lib_deps = - # renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library - adafruit/Adafruit BMP3XX Library@2.1.6 - # renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X - adafruit/Adafruit MAX1704X@1.0.3 - # renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library - adafruit/Adafruit SHTC3 Library@1.0.2 - # renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X - adafruit/Adafruit LPS2X@2.0.6 - # renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library - adafruit/Adafruit SHT31 Library@2.2.2 - # renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library - adafruit/Adafruit VEML7700 Library@2.1.6 - # renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library - adafruit/Adafruit SHT4x Library@1.0.5 - # renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library - sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6 - # renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001 - closedcube/ClosedCube OPT3001@1.1.2 - # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master - https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip - # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core - sensirion/Sensirion Core@0.7.3 - # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x - sensirion/Sensirion I2C SCD4x@1.1.0 - # renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x - sensirion/Sensirion I2C SFA3x@1.0.0 - # renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30 - sensirion/Sensirion I2C SCD30@1.0.0 \ No newline at end of file + ${environmental_extra_common.lib_deps} + # renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680 + adafruit/Adafruit BME680 Library@^2.0.5 diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 3a1eb9532..c202028e1 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -8,6 +8,10 @@ #include "SPILock.h" #include "TelemetrySensor.h" +#if __has_include() +#include +#endif + BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} #if __has_include() @@ -96,8 +100,28 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.temperature = bme680->readTemperature(); measurement->variant.environment_metrics.relative_humidity = bme680->readHumidity(); measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F; - measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0; + float gasRaw = bme680->readGas(); + measurement->variant.environment_metrics.gas_resistance = gasRaw / 1000.0; + + // IAQ approximation: humidity-compensated logarithmic mapping of gas resistance + // Gas sensor resistance drops with humidity; compensate to a 40% RH reference baseline + // Map compensated gas resistance (Ohms) to IAQ 0-500 using log-linear interpolation + // Clean air reference ~400 kOhm, polluted reference ~5 kOhm + if (gasRaw > 0.0f && !isfinite(gasRaw)) { + + static constexpr float LOG_UPPER = 12.899219f; // log(400k) + static constexpr float LOG_RANGE_INV = 1.0f / (12.899219f - 8.517193f); // 1 / (log(400k) - log(5k)) + measurement->variant.environment_metrics.has_iaq = true; + measurement->variant.environment_metrics.iaq = (uint16_t)(fminf( + fmaxf(((LOG_UPPER - + logf(fmaxf(gasRaw * expf(0.035f * (measurement->variant.environment_metrics.relative_humidity - 40.0f)), + 1.0f))) * + LOG_RANGE_INV) * + 500.0f, + 0.0f), + 500.0f)); + } #endif return true; } diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index eaeceb848..1134f04d9 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -28,7 +28,7 @@ class BME680Sensor : public TelemetrySensor #else using BME680Ptr = std::unique_ptr; - static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique(bus); } + static BME680Ptr makeBME680(TwoWire *bus) { return BME680Ptr(new Adafruit_BME680(bus)); } BME680Ptr bme680; #endif From 5408e81de71830a9a0ed881cc3c7db988f0dc779 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 06:44:55 -0600 Subject: [PATCH 170/223] Upgrade trunk (#9683) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 215b31a02..3e94ab755 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,7 +9,7 @@ plugins: lint: enabled: - checkov@3.2.502 - - renovate@43.19.2 + - renovate@43.24.2 - prettier@3.8.1 - trufflehog@3.93.3 - yamllint@1.38.0 From 86986d6337455bd2ca7cc8dfe2b7874306ea62da Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 19 Feb 2026 05:20:07 +0300 Subject: [PATCH 171/223] ULED_BUILTIN for 9m2ibr_aprs_lora_tracker (#9685) --- variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini index 2ddc5a2db..3fdb738fc 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini @@ -10,6 +10,7 @@ build_flags = -D EBYTE_E22 -D EBYTE_E22_900M30S ; Assume Tx power curve is identical to 900M30S as there is no documentation -I variants/esp32/diy/9m2ibr_aprs_lora_tracker + -ULED_BUILTIN build_src_filter = ${esp32_base.build_src_filter} +<../variants/esp32/diy/9m2ibr_aprs_lora_tracker> \ No newline at end of file From d0cf79a9912645a64dccad8776feb21d63777de8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:03:53 -0600 Subject: [PATCH 172/223] Upgrade trunk (#9692) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 3e94ab755..77b256a18 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,8 +8,8 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.502 - - renovate@43.24.2 + - checkov@3.2.504 + - renovate@43.25.8 - prettier@3.8.1 - trufflehog@3.93.3 - yamllint@1.38.0 From 4c91beeda9dac60f6fa517dbdc145657f869a452 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 19 Feb 2026 07:16:33 -0600 Subject: [PATCH 173/223] Develop to master (#9618) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don't ever define PIN_LED or BLE_LED_INVERTED (#9494) * Don't ever define PIN_LED * Deprecate BLE_LED_INVERTED * Add StatusMessage module and config overrides (#9351) * Add StatusMessage module and config overrides * Trunk * Don't reboot node simply for a StatusMessage config update * Missed in reviews - fixing send bubble (#9505) * Prefer EXT_PWR_DETECT pin over chargingVolt to detect power unplugged (#9511) * Make sure we always return a value in NodeDB::restorePreferences() (#9516) In case FScom is not defined there is no return statement. This moves the return outside of the ifdef to make sure a defined value is returned. * Inkhud battery icon improvements. (#9513) * Inkhud battery icon improvements. Fixes the battery icon draining from the flat side towards the bump, which is backwards from general design language seen on most devices By request of kr0n05_ on discord, adds the ability to mirror the battery icon which fixes that issue in another way, and is also a common design seen on other devices. * Remove option for icon mirroring * Add border + dither to battery to prevent font overlap * Fix trunk format * Code cleanup, courtesy of Xaositek. * Add reply bot module with DM-only responses and rate limiting (#9456) * Implement Meshtastic reply bot module with ping and status features Adds a reply bot module that listens for /ping, /hello, and /test commands received via direct messages or broadcasts on the primary channel. The module always replies via direct message to the sender only, reporting hop count, RSSI, and SNR. Per-sender cooldowns are enforced to reduce network spam, and the module can be excluded at build time via a compile flag. Updates include the new module source files and required build configuration changes. * Update ReplyBotModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/ReplyBotModule.h Match the existing MESHTASTIC_EXCLUDE_* guard pattern so the module is excluded by default. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/ReplyBotModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Tidying up --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors * HotFix for ReplyBot - Modules.cpp included and moved configuration.h (#9532) * Undefine LED_BUILTIN (#9531) Keep variant in sync with https://github.com/meshtastic/firmware/commit/df40085 Co-authored-by: Ben Meadors * Add agc reset attempt (#8163) * Add agc reset attempt * Add radioLibInterface include * Trunk * AGC reset don't crash, don't naively call * Update src/main.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Use Throttle function --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove unused hmx variable (#9529) The variable is not used at all in the function, remove it to silence the compiler warning. Co-authored-by: Ben Meadors * Rename LED_PIN to LED_POWER, move handling out of main to dedicated module (#9512) * Rename LED_PIN to LED_POWER, move handling out of main to dedicated module * Misc * Remove errant endif * Fix hop_limit upgrade detection (#9550) Co-authored-by: Ben Meadors * meshtasticd: Fix install on Fedora 43 (#9556) * RPM: Include meshtasticd-start.sh (#9561) * Add Slash Key to VirtualKeyboard (#9563) Addition of ? and / to the virtual Keyboard via short and long press * Add support for CW2015 LiPo battery fuel gauge (#9564) * Add support for CW2015 LiPo battery fuel gauge * Address Copilot's concerns, minor fixups * Make LED_POWER blip even in critical battery (#9545) * Enable FORTIFY and SP for native builds (#9537) * Enable FORITFY and NX for native builds meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack. This gives the following improvements on Debian Trixie: $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd Tested with --sim mode I do not get any crashes or similar. * Enable FORTIFY and NX for native builds meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack. This gives the following improvements on Debian Trixie: $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd Tested with --sim mode I do not get any crashes or similar. * Enable FORTIFY and SP for native builds meshtasticd does have a stack canaries and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and stack canaries. This gives the following improvements on Debian Trixie: $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 13516 Symbols No 0 17 ./.pio/build/native/meshtasticd $ checksec --file=./.pio/build/native/meshtasticd RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 13519 Symbols Yes 12 20 ./.pio/build/native/meshtasticd Tested with --sim mode I do not get any crashes or similar. --------- Co-authored-by: Ben Meadors * Update built-in documentation for current method of implementation (#9592) * Refactor logging in ProtobufModule to ensure message details are logged after successful decoding (#9536) * Automated version bumps (#9604) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Add missing openocd_target to custom nrf52 boards (#9603) This stops platformio complaining about `Missing target configuration for me25ls01-4y10td` etc when trying to flash with nrfutil. * Add sdl libs for native builds (#9595) * Add sdl libs for native builds * Alpine try again * fix some random compiler warnings (#9596) * Modify the dependency library of v4-tft (#9507) * BaseUI: Favorite Screen Signal Quality improvement (#9566) * Favorite Signal Quality improvement * Show Voltage if node shares it. * Trunk Fix * Change Favorite tittle to encase name with Asterisks * Add Pluggin In condition for Battery Line * Adjust getUptimeStr Prefixes * Create isAPIConnected for SharedCommon usage * Correct leftSideSpacing to account for isAPIConnected --------- Co-authored-by: Jason P * ExternalNotification and StatusLED now call AmbientLighting to update… (#9554) * ExternalNotification and StatusLED now call AmbientLighting to update RGB LEDs. Add optional heartbeat * Don't overwrite RGB state if heartbeat is disabled. * Use the right define * Remove another .h and make rgb static * move rgb objects into AmbientLighting class * Straighten out AmbientLighting Thread object * Use %f for floats * Fixes on SCD4X admin comands (#9607) * Fixes on SCD4X admin comands * Minor fix in logs for SEN5X --------- Co-authored-by: Ben Meadors * feat/add sfa30 (#9372) * Move PMSA003I to separate class and update AQ telemetry * AirQualityTelemetry module not depend on PM sensor presence * Remove commented line * Fixes on PMS class * Add missing warmup period to wakeUp function * Fixes on compilation for different variants * Add functions to check for I2C bus speed and set it * Initial implementation for SFA30Sensor * Move PMSA003I to separate class and update AQ telemetry * AirQualityTelemetry module not depend on PM sensor presence * Remove commented line * Fixes on PMS class * Add missing warmup period to wakeUp function * Fixes on compilation for different variants * Add functions to check for I2C bus speed and set it * Add ScreenFonts.h Co-authored-by: Hannes Fuchs * PMSA003I 1st round test * Fix I2C scan speed * Fix minor issues and bring back I2C SPEED def * Remove PMSA003I library as its no longer needed * Add functional SCD4X * Fix screen frame for CO2 * Add admin commands to SCD4X class * Add further admin commands and fixes. * Remove unused I2C speed functions and cleanup * Cleanup of SEN5X specific code added from switching branches * Remove SCAN_I2C_CLOCK_SPEED block as its not needed * Remove associated functions for setting I2C speed * Unify build epoch to add flag in platformio-custom.py (#7917) * Unify build_epoch replacement logic in platformio-custom * Missed one * Fix build error in rak_wismesh_tap_v2 (#7905) In the logs was: "No screen resolution defined in build_flags. Please define DISPLAY_SIZE." set according to similar devices. * Put guards in place around debug heap operations (#7955) * Put guards in place around debug heap operations * Add macros to clean up code * Add pointer as well * Cleanup * Fix memory leak in NextHopRouter: always free packet copy when removing from pending * Formatting * Only queue 2 client notification * Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap * Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap * Update protobufs (#7973) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs * Trunk * Trunk * Static memory pool allocation (#7966) * Static memory pool * Initializer * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs --------- Co-authored-by: WillyJL * Portduino dynamic alloc * Missed * Drop the limit * Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix json report crashes on esp32 (#7978) * Tweak maximums * Fix DRAM overflow on old esp32 targets * Guard bad time warning logs using GPS_DEBUG (#7897) In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy. In combination, these result in a spamming of the logs when a bad time is found When the GPS is active, we're calling the GPS thread every 0.2secs. So this log could be printed 4,500 times in a no-lock scenario :) Reserve this experience for developers using GPS_DEBUG. Fixes https://github.com/meshtastic/firmware/issues/7896 * Scale probe buffer size based on current baud rate (#7975) * Scale probe buffer size based on current baud rate * Throttle bad time validation logging and fix time comparison logic * Remove comment * Missed the other instances * Copy pasta * Fix GPS gm_mktime memory leak (#7981) * Fix overflow of time value (#7984) * Fix overflow of time value * Revert "Fix overflow of time value" This reverts commit 084796920179e80a7500d36c25fd4d82b3ef4214. * That got boogered up * Remove PMSA003 include from modules * Add flag to exclude air quality module * Rework PMSA003I to align with new I2C scanner * Reworks AQ telemetry to match new dynamic allocation method * Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements * Reworks PMSA003I * Move add sensor template to separate file * Split telemetry on screen options * Add variable I2C clock compile flag * Added to Seeed Xiao S3 as demo * Fix drawFrame in AQ module * Module settings override to i2cScan module function * Move to CAN_RECLOCK_I2C per architecture * Add reclock function in TelemetrySensor.cpp * Add flag in ESP32 common * Minor fix * Move I2C reclock function to src/detect * Fix uninitMemberVar errors and compile issue * Make sleep, wakeUp functions generic * Fix STM32 builds * Add exclude AQ sensor to builds that have environmental sensor excludes * Add includes to AddI2CSensorTemplate.h * SEN5X first pass * WIP Sen5X functions * Further (non-working) progress in SEN5X * WIP Sen5X functions * Changes on SEN5X library - removing pm_env as well * Small cleanup of SEN5X sensors * Minor change for SEN5X detection * Remove dup code * Enable PM sensor before sending telemetry. This enables the PM sensor for a predefined period to allow for warmup. Once telemetry is sent, the sensor shuts down again. * Small cleanups in SEN5X sensor * Add dynamic measurement interval for SEN5X * Only disable SEN5X if enough time after reading. * Idle for SEN5X on communication error * Cleanup of logs and remove unnecessary delays * Small TODO * Settle on uint16_t for SEN5X PM data * Make AQTelemetry sensors non-exclusive * Implementation of cleaning in FS prefs and cleanup * Remove unnecessary LOGS * Add cleaning date storage in FS * Report non-cumulative PN * Bring back detection code for SEN5X after branch rebase * Add placeholder for admin message * Add VOC measurements and persistence (WIP) * Adds VOC measurements and state * Still not working on VOC Index persistence * Should it stay in continuous mode? * Add one-shot mode config flag to SEN5X * Add nan checks on sensor data from SEN5X * Working implementation on VOCState * Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h) * Adds conditions for stability and sensor state * Fixes on VOC state and mode swtiching * Adds a new RHT/Gas only mode, with 3600s stabilization time * Fixes the VOCState buffer mismatch * Fixes SEN50/54/55 model mistake * Adapt SEN5X to new sensor list structure. Improve reclock. * Improve reClockI2C conditions for different variants * Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery * Add SEN5X * Fix merge errors * Small reordering in PMS class for consistency * If one sensor fails, AQ telemetry still reports data * Small formatting fix * Add SEN5X to AQI in ScanI2C * SCD4X now part of AQ module with template list * Fixes difference between idle and sleep * In LowPower, sleep is disabled * Requires testing for I2C clock comms for commands * Remove unnecessary import * Add co2 to serialized AQ metrics * Add SFA30 with new sensor template in AQ module * Update library dependencies in platformio.ini * Fix unitialized variables in SEN5X constructor * Fix missing import * Fix uninitMemberVars * Fix import error for SCD4X * Fix I2CClock logic * Fix not reclocking back to 700000Hz * Fix multiple sensors being read simultaneously * The logic in AQ module is different to the one in EnvironmentTelemetryModule. In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data. * Fix pending clock change in PMSA003 * Cleanup of SEN5X class * Exclude AQ sensor from wio-e5 due to flash limitations * Fix I2C clock change logic * Make sure clock is always set to needed value * Fix returns * Fix trunk * Fix on condition in reclock * Fix trunk * Final SFA30 class implementation * Add HCHO to screen and improve logs * Add metrics to mesh packet serializer * Minor fixes in logs * OCD tidy up of logs * Fix sleep function * Remove old I2C_CLOCK_SPEED code --------- Co-authored-by: nikl Co-authored-by: Hannes Fuchs Co-authored-by: Nashui-Yan Co-authored-by: Ben Meadors Co-authored-by: Tom Fifield Co-authored-by: Mike Robbins Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: WillyJL Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Log rxBad PacketHeaders with more info (id, relay_node) like printPacket, so we can try to match RX errors to other packets in the logs. (#9614) * Exclude status message module * fix: zero entire public key array instead of only first byte (#9619) * Update protobufs (#9621) * Xiao NRF - define suitable i2c pins for the sub-variants (#8866) Co-authored-by: Christian Walther * Update src/detect/ScanI2C.cpp Co-authored-by: Wessel * Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp Co-authored-by: Wessel * Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp Co-authored-by: Wessel * Update src/modules/Telemetry/Sensor/SFA30Sensor.cpp Co-authored-by: Wessel * Update src/mesh/NodeDB.cpp Co-authored-by: Wessel * convert GPS global and some new in gps.cpp to unique_ptr (#9628) Trying this to see if anything bad happen if I were to replace most raw pointers with unique_ptr. I didn't used std::make_unique since it is only supported in C++14 and onwards but until we update esp32 to arduino 3.x the ESP32 xtensa chips use a C++11 std. * replace delete in RedirectablePrint.cpp with std::unique_ptr (#9642) Is part of the unique_ptr modernization effort. * replace delete in EInkDynamicDisplay.{cpp,h} with std::unique_ptr (#9643) Is part of the unique_ptr modernization effort. * Undefine LED_BUILTIN for Heltec v2 variant (#9647) * Undefine LED_BUILTIN for Heltec v2 variant * Undefine LED_BUILTIN for Heltec v2.1 variant --------- Co-authored-by: Jorropo * replace delete in RadioInterface.cpp with std::unique_ptr (#9645) Is part of the unique_ptr modernization effort. * fix typo in PIN_GPS_SWITCH (#9648) Wasn't caught by CI. * replace delete in CryptoEngine.{cpp,h} with std::unique_ptr (#9649) Is part of the unique_ptr modernization effort. * workaround NCP5623 and LP5562 I2C builds (#9652) Theses two appear to be buggy on r1-neo and nomadstar meteor pro, they rely on Wire.h being included previously to their import. Idk why other platforms using the same smart LEDs are working while theses ones don't. This should make CI green on the dev branch. * replace delete in AudioThread.h with std::unique_ptr (#9651) Is part of the unique_ptr modernization effort. * Add USB_MODE=1 for Station G2 (#9660) * InkHUD: Favorite Map Applet (#9654) * fix a lot of low level cppcheck warnings (#9623) * simplify the observer pattern, since all the called functions are const getters. * use arduino macro over std: for numerical values and refactor local variables in drawScrollbar() * oh, so Cppcheck actually complained about const pointers not being const. * slowly getting out of ifdef hell * fix inkHUD warnings as well * last 2 check warnings * git checks should fail on low defects from now on * Feat/add scd30 (#9609) * Merge develop into SCD30 * Add SCD30 class * Fix logging and admin commands * Minor cleanup and logging improvements * Minor formatting issue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Improvements on setTemperature * Fix casting float-uint16_t * Pass 100 for resetting temperature offset * Fix issues pointed out by copilot * Add quick reboot to set interval quicker on scd30 * Change saveState to only happen after boot and minor log changes * Fix missing semicolon in one shot mode log --------- Co-authored-by: Thomas Göttgens Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors * fix: respect DontMqttMeBro flag regardless of channel PSK (#9626) The previous PSK check was broken from its introduction in #4643 — memcmp was used in boolean context without comparing to 0, inverting the condition. Since no one noticed for over a year, the PSK-based filtering provided no practical value. Simplifying to always respect the sender's preference is both more correct and easier to reason about. * our firmware action is too clever Update pio_target and add pio_opts for checks. * fix detection of SCD30 by checking if the size of the return from a 2 byte register read is correct (#9664) * fix detection of SCD30 by checking if thee size of the return from a 2 byte register read is correct fix signedness warning in PMSA003 sensor code. * Add alternate path for LPS22HB Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * check EndTransmission for errors and compare returned length to expected value Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * #9623 resolved a local shadow of next_key by converting it to int. (#9665) * zip a few gitrefs down (#9672) * InkHUD: Allow non-system applets to subscribe to input events (#9514) * Allow inkhud user applets to consume inputs with opt-in system Adds a way for applets to subscribe to input events while keeping it off by default to preserve compatibility and expected behaviours. Adds example for use as well. * Add check for nullptr on getActiveApplet uses * Remove redundant includes * Move subscribedInputs to protected * More consistent naming scheme * Fake IAQ values on Non-BSEC2 platforms like Platformio and the original ESP32 (#9663) * BSEC2 Replacement - add approximation for IAQ to non-BSEC2 platforms. - Re-add this sensor to ESP32 targets, and refactor env_extra includes. - Fix C++ 11 compatibility * Check for gas resistance 0 * ULED_BUILTIN for 9m2ibr_aprs_lora_tracker (#9685) --------- Co-authored-by: Jonathan Bennett Co-authored-by: Jason P Co-authored-by: Eric Sesterhenn Co-authored-by: Vortetty <33466216+Vortetty@users.noreply.github.com> Co-authored-by: Mattatat25 <108779801+mattatat25@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Max Co-authored-by: Colby Dillion Co-authored-by: Austin Co-authored-by: Tom <116762865+NomDeTom@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Chloe Bethel Co-authored-by: Thomas Göttgens Co-authored-by: Quency-D <55523105+Quency-D@users.noreply.github.com> Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Co-authored-by: oscgonfer Co-authored-by: nikl Co-authored-by: Hannes Fuchs Co-authored-by: Nashui-Yan Co-authored-by: Tom Fifield Co-authored-by: Mike Robbins Co-authored-by: WillyJL Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Wessel Co-authored-by: Christian Walther Co-authored-by: Jorropo Co-authored-by: Eric Barch Co-authored-by: Clive Blackledge --- .clusterfuzzlite/Dockerfile | 2 +- .github/workflows/main_matrix.yml | 1 + Dockerfile | 4 +- alpine.Dockerfile | 4 +- boards/station-g2.json | 2 +- debian/control | 3 +- meshtasticd.spec.rpkg | 10 +- platformio.ini | 56 +- protobufs | 2 +- src/AmbientLightingThread.h | 127 +++-- src/AudioThread.h | 23 +- src/BluetoothStatus.h | 12 +- src/Led.cpp | 66 --- src/Led.h | 7 - src/Power.cpp | 109 +++- src/PowerFSM.cpp | 8 +- src/RedirectablePrint.cpp | 47 +- src/configuration.h | 3 + src/detect/ScanI2C.cpp | 4 +- src/detect/ScanI2C.h | 5 +- src/detect/ScanI2CTwoWire.cpp | 50 +- src/detect/ScanI2CTwoWire.h | 2 + src/gps/GPS.cpp | 22 +- src/gps/GPS.h | 6 +- src/gps/RTC.cpp | 2 +- src/gps/RTC.h | 2 +- src/graphics/EInkDynamicDisplay.cpp | 6 +- src/graphics/EInkDynamicDisplay.h | 11 +- src/graphics/NeoPixel.h | 4 - src/graphics/NomadStarLED.h | 2 + src/graphics/RAKled.h | 5 - src/graphics/SharedUIDisplay.cpp | 51 +- src/graphics/SharedUIDisplay.h | 14 + src/graphics/TimeFormatters.cpp | 12 +- src/graphics/VirtualKeyboard.cpp | 14 +- src/graphics/draw/DebugRenderer.cpp | 2 +- src/graphics/draw/MenuHandler.cpp | 23 +- src/graphics/draw/NodeListRenderer.cpp | 23 +- src/graphics/draw/UIRenderer.cpp | 248 +++++++-- src/graphics/draw/UIRenderer.h | 2 +- src/graphics/images.h | 6 + .../Drivers/Backlight/LatchingBacklight.cpp | 8 +- .../Drivers/Backlight/LatchingBacklight.h | 2 +- src/graphics/niche/InkHUD/Applet.cpp | 57 +- src/graphics/niche/InkHUD/Applet.h | 39 +- src/graphics/niche/InkHUD/AppletFont.cpp | 14 +- src/graphics/niche/InkHUD/AppletFont.h | 11 +- .../Applets/Bases/NodeList/NodeListApplet.cpp | 14 +- .../UserAppletInputExample.cpp | 79 +++ .../UserAppletInputExample.h | 36 ++ .../System/BatteryIcon/BatteryIconApplet.cpp | 37 +- .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 8 +- .../InkHUD/Applets/System/Menu/MenuApplet.h | 2 +- .../Notification/NotificationApplet.cpp | 10 +- .../Applets/System/Pairing/PairingApplet.cpp | 6 +- .../User/FavoritesMap/FavoritesMapApplet.cpp | 111 ++++ .../User/FavoritesMap/FavoritesMapApplet.h | 44 ++ .../InkHUD/Applets/User/Heard/HeardApplet.cpp | 7 +- .../ThreadedMessage/ThreadedMessageApplet.cpp | 2 +- src/graphics/niche/InkHUD/Events.cpp | 73 ++- src/graphics/niche/InkHUD/InkHUD.cpp | 6 + src/graphics/niche/InkHUD/InkHUD.h | 1 + src/graphics/niche/InkHUD/MessageStore.cpp | 28 +- src/graphics/niche/InkHUD/MessageStore.h | 2 +- .../niche/InkHUD/PlatformioConfig.ini | 4 +- src/graphics/niche/InkHUD/Renderer.cpp | 26 +- src/graphics/niche/InkHUD/WindowManager.cpp | 16 +- src/graphics/niche/InkHUD/WindowManager.h | 1 + .../niche/Utils/CannedMessageStore.cpp | 2 +- src/input/HackadayCommunicatorKeyboard.cpp | 9 +- src/input/HackadayCommunicatorKeyboard.h | 4 +- src/input/MPR121Keyboard.cpp | 6 +- src/input/MPR121Keyboard.h | 2 +- src/input/TCA8418Keyboard.cpp | 11 +- src/input/TCA8418Keyboard.h | 4 +- src/input/TDeckProKeyboard.cpp | 9 +- src/input/TDeckProKeyboard.h | 4 +- src/input/TLoraPagerKeyboard.cpp | 9 +- src/input/TLoraPagerKeyboard.h | 4 +- src/main.cpp | 112 ++-- src/main.h | 1 + src/mesh/CryptoEngine.cpp | 11 +- src/mesh/CryptoEngine.h | 4 +- src/mesh/NodeDB.cpp | 24 +- src/mesh/PacketHistory.cpp | 4 +- src/mesh/PacketHistory.h | 4 +- src/mesh/ProtobufModule.h | 3 +- src/mesh/RadioInterface.cpp | 118 ++-- src/mesh/RadioInterface.h | 3 +- src/mesh/RadioLibInterface.cpp | 9 +- src/mesh/Router.h | 5 +- src/mesh/StreamAPI.cpp | 4 +- src/mesh/StreamAPI.h | 4 +- src/mesh/aes-ccm.cpp | 2 +- src/mesh/api/PacketAPI.h | 2 +- src/mesh/generated/meshtastic/admin.pb.cpp | 3 + src/mesh/generated/meshtastic/admin.pb.h | 57 +- src/mesh/generated/meshtastic/telemetry.pb.h | 8 +- src/mesh/http/ContentHandler.cpp | 45 +- src/mesh/http/ContentHandler.h | 1 - src/modules/AdminModule.cpp | 26 +- src/modules/CannedMessageModule.cpp | 11 +- src/modules/ExternalNotificationModule.cpp | 100 +--- src/modules/ExternalNotificationModule.h | 5 + src/modules/Modules.cpp | 15 +- src/modules/NodeInfoModule.cpp | 2 +- src/modules/PowerStressModule.cpp | 7 +- src/modules/ReplyBotModule.cpp | 183 +++++++ src/modules/ReplyBotModule.h | 19 + src/modules/StatusLEDModule.cpp | 93 +++- src/modules/StatusLEDModule.h | 17 +- src/modules/StatusMessageModule.cpp | 41 ++ src/modules/StatusMessageModule.h | 35 ++ src/modules/StoreForwardModule.cpp | 4 +- src/modules/Telemetry/AirQualityTelemetry.cpp | 50 +- src/modules/Telemetry/AirQualityTelemetry.h | 3 +- .../Telemetry/Sensor/AddI2CSensorTemplate.h | 2 +- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 26 +- src/modules/Telemetry/Sensor/BME680Sensor.h | 2 +- .../Telemetry/Sensor/PMSA003ISensor.cpp | 8 +- src/modules/Telemetry/Sensor/SCD30Sensor.cpp | 511 ++++++++++++++++++ src/modules/Telemetry/Sensor/SCD30Sensor.h | 53 ++ src/modules/Telemetry/Sensor/SCD4XSensor.cpp | 112 ++-- src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 68 ++- src/modules/Telemetry/Sensor/SEN5XSensor.h | 2 +- src/modules/Telemetry/Sensor/SFA30Sensor.cpp | 198 +++++++ src/modules/Telemetry/Sensor/SFA30Sensor.h | 39 ++ src/modules/TraceRouteModule.cpp | 2 +- src/modules/TraceRouteModule.h | 2 +- src/mqtt/MQTT.cpp | 6 +- src/nimble/NimbleBluetooth.cpp | 6 +- src/platform/esp32/MeshtasticOTA.cpp | 2 +- src/platform/esp32/MeshtasticOTA.h | 2 +- src/platform/nrf52/AsyncUDP.h | 2 +- src/platform/nrf52/architecture.h | 2 +- src/platform/portduino/PortduinoGlue.cpp | 20 +- src/platform/portduino/PortduinoGlue.h | 53 +- src/platform/portduino/USBHal.h | 2 +- src/power.h | 6 +- src/serialization/MeshPacketSerializer.cpp | 9 + .../MeshPacketSerializer_nRF52.cpp | 9 + src/sleep.cpp | 5 +- .../esp32/betafpv_2400_tx_micro/variant.h | 2 +- variants/esp32/betafpv_900_tx_nano/variant.h | 2 +- variants/esp32/chatter2/variant.h | 2 - .../9m2ibr_aprs_lora_tracker/platformio.ini | 1 + .../diy/9m2ibr_aprs_lora_tracker/variant.h | 4 +- variants/esp32/diy/hydra/variant.h | 2 +- variants/esp32/diy/v1/variant.h | 2 +- variants/esp32/hackerboxes_esp32_io/variant.h | 2 +- variants/esp32/heltec_v1/variant.h | 2 +- variants/esp32/heltec_v2.1/platformio.ini | 1 + variants/esp32/heltec_v2.1/variant.h | 2 +- variants/esp32/heltec_v2/platformio.ini | 1 + variants/esp32/heltec_v2/variant.h | 2 +- .../esp32/heltec_wireless_bridge/variant.h | 2 +- variants/esp32/heltec_wsl_v2.1/variant.h | 2 +- variants/esp32/m5stack_coreink/variant.h | 2 +- .../radiomaster_900_bandit_nano/variant.h | 2 +- variants/esp32/rak11200/variant.h | 2 +- variants/esp32/tbeam/variant.h | 4 +- variants/esp32/tlora_v1/variant.h | 2 +- variants/esp32/tlora_v1_3/variant.h | 2 +- variants/esp32/tlora_v2/variant.h | 2 +- variants/esp32/tlora_v2_1_16/platformio.ini | 2 +- variants/esp32/tlora_v2_1_16/variant.h | 6 +- .../esp32/tlora_v2_1_16_tcxo/platformio.ini | 3 +- variants/esp32/tlora_v2_1_18/variant.h | 2 +- variants/esp32/trackerd/variant.h | 2 +- variants/esp32/wiphone/variant.h | 1 - variants/esp32c3/ai-c3/variant.h | 2 +- .../hackerboxes_esp32c3_oled/variant.h | 2 +- variants/esp32c3/heltec_esp32c3/variant.h | 2 +- .../esp32c6/m5stack_unitc6l/platformio.ini | 2 +- variants/esp32c6/tlora_c6/variant.h | 2 +- variants/esp32s2/nugget_s2_lora/variant.h | 2 +- variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h | 2 +- variants/esp32s3/CDEBYTE_EoRa-S3/variant.h | 2 +- variants/esp32s3/EBYTE_ESP32-S3/variant.h | 2 +- .../esp32s3/ELECROW-ThinkNode-M2/variant.h | 2 +- .../esp32s3/ELECROW-ThinkNode-M5/variant.cpp | 6 + .../esp32s3/ELECROW-ThinkNode-M5/variant.h | 6 +- variants/esp32s3/bpi_picow_esp32_s3/variant.h | 2 +- .../crowpanel-esp32s3-5-epaper/variant.h | 2 +- .../esp32s3/diy/my_esp32s3_diy_eink/variant.h | 2 +- .../esp32s3/diy/my_esp32s3_diy_oled/variant.h | 2 +- variants/esp32s3/dreamcatcher/variant.h | 2 +- variants/esp32s3/esp32-s3-pico/variant.h | 1 - .../heltec_capsule_sensor_v3/variant.h | 4 +- variants/esp32s3/heltec_v3/variant.h | 2 +- variants/esp32s3/heltec_v4/platformio.ini | 2 +- .../heltec_vision_master_e213/nicheGraphics.h | 4 +- .../heltec_vision_master_e213/variant.h | 2 +- .../heltec_vision_master_e290/nicheGraphics.h | 4 +- .../heltec_vision_master_e290/variant.h | 2 +- .../heltec_wireless_paper/nicheGraphics.h | 4 +- .../esp32s3/heltec_wireless_paper/variant.h | 2 +- .../heltec_wireless_paper_v1/variant.h | 2 +- .../esp32s3/heltec_wireless_tracker/variant.h | 2 +- .../heltec_wireless_tracker_V1_0/variant.h | 2 +- .../heltec_wireless_tracker_v2/variant.h | 2 +- variants/esp32s3/heltec_wsl_v3/variant.h | 2 +- variants/esp32s3/mesh-tab/variant.h | 2 +- variants/esp32s3/nibble_esp32/variant.h | 2 +- variants/esp32s3/nugget_s3_lora/variant.h | 2 +- variants/esp32s3/rak3312/variant.h | 2 +- variants/esp32s3/rak_wismesh_tap_v2/variant.h | 2 +- variants/esp32s3/seeed_xiao_s3/variant.h | 2 +- variants/esp32s3/station-g2/platformio.ini | 4 +- variants/esp32s3/t-beam-1w/variant.h | 2 +- variants/esp32s3/t-eth-elite/variant.h | 2 +- .../esp32s3/tlora_t3s3_epaper/nicheGraphics.h | 4 +- variants/esp32s3/tlora_t3s3_epaper/variant.h | 2 +- variants/esp32s3/tlora_t3s3_v1/variant.h | 2 +- .../esp32s3/tracksenger/internal/variant.h | 2 +- variants/esp32s3/tracksenger/lcd/variant.h | 2 +- variants/esp32s3/tracksenger/oled/variant.h | 2 +- variants/esp32s3/unphone/platformio.ini | 4 +- variants/esp32s3/unphone/variant.h | 2 +- variants/native/portduino.ini | 2 + .../ELECROW-ThinkNode-M1/nicheGraphics.h | 18 +- .../nrf52840/ELECROW-ThinkNode-M3/variant.h | 1 - .../nrf52840/ELECROW-ThinkNode-M6/variant.cpp | 3 - .../nrf52840/ELECROW-ThinkNode-M6/variant.h | 2 +- variants/nrf52840/ME25LS01-4Y10TD/variant.cpp | 3 - variants/nrf52840/ME25LS01-4Y10TD/variant.h | 2 +- .../ME25LS01-4Y10TD_e-ink/variant.cpp | 3 - .../nrf52840/ME25LS01-4Y10TD_e-ink/variant.h | 2 +- variants/nrf52840/MS24SF1/variant.h | 2 +- .../nrf52_promicro_diy_tcxo/nicheGraphics.h | 6 +- .../seeed-xiao-nrf52840-wio-sx1262/README.md | 44 +- .../platformio.ini | 18 +- .../variant.cpp | 62 --- .../seeed-xiao-nrf52840-wio-sx1262/variant.h | 185 ------- .../seeed_xiao_nrf52840_e22/platformio.ini | 6 +- variants/nrf52840/diy/xiao_ble/platformio.ini | 52 +- .../nicheGraphics.h | 6 +- .../heltec_mesh_pocket/nicheGraphics.h | 4 +- .../heltec_mesh_solar/nicheGraphics.h | 4 +- .../platformio.ini | 4 +- .../nrf52840/seeed_solar_node/variant.cpp | 1 - .../seeed_wio_tracker_L1_eink/nicheGraphics.h | 4 + .../seeed_xiao_nrf52840_kit/platformio.ini | 13 +- .../seeed_xiao_nrf52840_kit/variant.h | 116 +++- variants/nrf52840/t-echo-lite/variant.h | 1 - variants/nrf52840/t-echo-plus/nicheGraphics.h | 2 + variants/nrf52840/t-echo/nicheGraphics.h | 4 +- variants/nrf52840/tracker-t1000-e/variant.cpp | 4 - variants/nrf52840/tracker-t1000-e/variant.h | 2 +- variants/nrf52840/wio-t1000-s/variant.cpp | 4 - variants/nrf52840/wio-t1000-s/variant.h | 2 +- .../challenger_2040_lora/pins_arduino.h | 2 +- .../rp2040/challenger_2040_lora/variant.h | 2 - variants/rp2040/ec_catsniffer/variant.h | 2 +- .../rp2040/feather_rp2040_rfm95/variant.h | 2 +- variants/rp2040/nibble_rp2040/variant.h | 2 +- variants/rp2040/rak11310/pins_arduino.h | 3 +- variants/rp2040/rak11310/variant.h | 2 +- variants/rp2040/rp2040-lora/variant.h | 2 +- variants/rp2040/rpipico-slowclock/variant.h | 2 +- variants/rp2040/rpipico/variant.h | 2 +- variants/rp2040/rpipicow/variant.h | 2 +- .../rp2040/senselora_rp2040/pins_arduino.h | 3 +- variants/rp2040/senselora_rp2040/variant.h | 2 +- variants/rp2350/rpipico2/variant.h | 2 +- variants/stm32/CDEBYTE_E77-MBL/variant.h | 4 +- variants/stm32/milesight_gs301/variant.h | 2 +- variants/stm32/rak3172/variant.h | 2 +- variants/stm32/russell/variant.h | 2 +- variants/stm32/wio-e5/variant.h | 2 +- 270 files changed, 3181 insertions(+), 1517 deletions(-) delete mode 100644 src/Led.cpp delete mode 100644 src/Led.h delete mode 100644 src/graphics/NeoPixel.h delete mode 100644 src/graphics/RAKled.h create mode 100644 src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp create mode 100644 src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h create mode 100644 src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp create mode 100644 src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h create mode 100644 src/modules/ReplyBotModule.cpp create mode 100644 src/modules/ReplyBotModule.h create mode 100644 src/modules/StatusMessageModule.cpp create mode 100644 src/modules/StatusMessageModule.h create mode 100644 src/modules/Telemetry/Sensor/SCD30Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SCD30Sensor.h create mode 100644 src/modules/Telemetry/Sensor/SFA30Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SFA30Sensor.h delete mode 100644 variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp delete mode 100644 variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile index 54b5cda0f..6114417c9 100644 --- a/.clusterfuzzlite/Dockerfile +++ b/.clusterfuzzlite/Dockerfile @@ -20,7 +20,7 @@ ENV PIP_ROOT_USER_ACTION=ignore RUN apt-get update && apt-get install --no-install-recommends -y \ cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \ libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \ - libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev && \ + libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev libsdl2-dev && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ pip install --no-cache-dir -U \ platformio==6.1.16 \ diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 6b48e8128..876d7e4ce 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -96,6 +96,7 @@ jobs: pio_platform: ${{ matrix.check.platform }} pio_env: ${{ matrix.check.board }} pio_target: check + pio_opts: --fail-on-defect=low build: needs: [setup, version] diff --git a/Dockerfile b/Dockerfile index 91d3f7796..e00d81658 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ curl wget g++ zip git ca-certificates pkg-config \ libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \ libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \ - libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev \ + libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir -U platformio \ && mkdir /tmp/firmware @@ -53,7 +53,7 @@ USER root RUN apt-get update && apt-get --no-install-recommends -y install \ libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \ liborcania2.3 libulfius2.7t64 libssl3t64 \ - libx11-6 libinput10 libxkbcommon-x11-0 \ + libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \ && apt-get clean && rm -rf /var/lib/apt/lists/* \ && mkdir -p /var/lib/meshtasticd \ && mkdir -p /etc/meshtasticd/config.d \ diff --git a/alpine.Dockerfile b/alpine.Dockerfile index 64c281788..75c9aa594 100644 --- a/alpine.Dockerfile +++ b/alpine.Dockerfile @@ -11,7 +11,7 @@ RUN apk --no-cache add \ bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \ libgpiod-dev yaml-cpp-dev bluez-dev \ libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \ - libx11-dev libinput-dev libxkbcommon-dev sqlite-dev \ + libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \ && rm -rf /var/cache/apk/* \ && pip install --no-cache-dir -U platformio \ && mkdir /tmp/firmware @@ -42,7 +42,7 @@ USER root RUN apk --no-cache add \ shadow libstdc++ libbsd libgpiod yaml-cpp libusb \ - i2c-tools libuv libx11 libinput libxkbcommon \ + i2c-tools libuv libx11 libinput libxkbcommon sdl2 \ && rm -rf /var/cache/apk/* \ && mkdir -p /var/lib/meshtasticd \ && mkdir -p /etc/meshtasticd/config.d \ diff --git a/boards/station-g2.json b/boards/station-g2.json index 871f067aa..f7ce50779 100755 --- a/boards/station-g2.json +++ b/boards/station-g2.json @@ -8,7 +8,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=0" ], diff --git a/debian/control b/debian/control index 46c932a80..8e5f17af9 100644 --- a/debian/control +++ b/debian/control @@ -26,7 +26,8 @@ Build-Depends: debhelper-compat (= 13), libx11-dev, libinput-dev, libxkbcommon-x11-dev, - libsqlite3-dev + libsqlite3-dev, + libsdl2-dev Standards-Version: 4.6.2 Homepage: https://github.com/meshtastic/firmware Rules-Requires-Root: no diff --git a/meshtasticd.spec.rpkg b/meshtasticd.spec.rpkg index fc14ede7f..a9eb552d7 100644 --- a/meshtasticd.spec.rpkg +++ b/meshtasticd.spec.rpkg @@ -49,6 +49,7 @@ BuildRequires: pkgconfig(libulfius) BuildRequires: pkgconfig(x11) BuildRequires: pkgconfig(libinput) BuildRequires: pkgconfig(xkbcommon-x11) +BuildRequires: pkgconfig(sdl2) # libbsd is needed on older Fedora/RHEL to provide 'strlcpy' %if 0%{?fedora} >= 39 || 0%{?rhel} >= 10 @@ -59,8 +60,14 @@ BuildRequires: pkgconfig(libbsd-overlay) Requires: systemd-udev +# Declare that this package provides the user/group it creates in %pre +# Required for Fedora 43+ which tracks users/groups as RPM dependencies +Provides: user(%{meshtasticd_user}) +Provides: group(%{meshtasticd_user}) +Provides: group(spi) + %description -Meshtastic daemon for controlling Meshtastic devices. Meshtastic is an off-grid +Meshtastic daemon. Meshtastic is an off-grid text communication platform that uses inexpensive LoRa radios. %prep @@ -151,6 +158,7 @@ fi %license LICENSE %doc README.md %{_bindir}/meshtasticd +%{_bindir}/meshtasticd-start.sh %dir %{_localstatedir}/lib/meshtasticd %{_udevrulesdir}/99-meshtasticd-udev.rules %dir %{_sysconfdir}/meshtasticd diff --git a/platformio.ini b/platformio.ini index 307fb052e..ccc5c9755 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,11 +50,13 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_APRS=1 -DRADIOLIB_EXCLUDE_LORAWAN=1 -DMESHTASTIC_EXCLUDE_DROPZONE=1 + -DMESHTASTIC_EXCLUDE_REPLYBOT=1 -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1 -DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1 -DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 + -DMESHTASTIC_EXCLUDE_STATUS=1 -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage -DLED_BUILTIN=-1 #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now @@ -182,8 +184,8 @@ lib_deps = # renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE wollewald/BH1750_WE@1.1.10 -; (not included in native / portduino) -[environmental_extra] +; Common environmental sensor libraries (not included in native / portduino) +[environmental_extra_common] lib_deps = # renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library adafruit/Adafruit BMP3XX Library@2.1.6 @@ -203,41 +205,29 @@ lib_deps = sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6 # renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001 closedcube/ClosedCube OPT3001@1.1.2 + # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master + https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip + # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core + sensirion/Sensirion Core@0.7.3 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x + sensirion/Sensirion I2C SCD4x@1.1.0 + # renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x + sensirion/Sensirion I2C SFA3x@1.0.0 + # renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30 + sensirion/Sensirion I2C SCD30@1.0.0 + +; Environmental sensors with BSEC2 (Bosch proprietary IAQ) +[environmental_extra] +lib_deps = + ${environmental_extra_common.lib_deps} # renovate: datasource=custom.pio depName=Bosch BSEC2 packageName=boschsensortec/library/bsec2 boschsensortec/bsec2@1.10.2610 # renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library boschsensortec/BME68x Sensor Library@1.3.40408 - # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master - https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip - # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core - sensirion/Sensirion Core@0.7.3 - # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x - sensirion/Sensirion I2C SCD4x@1.1.0 -; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) +; Environmental sensors without BSEC (saves ~3.5KB DRAM for original ESP32 targets) [environmental_extra_no_bsec] lib_deps = - # renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library - adafruit/Adafruit BMP3XX Library@2.1.6 - # renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X - adafruit/Adafruit MAX1704X@1.0.3 - # renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library - adafruit/Adafruit SHTC3 Library@1.0.2 - # renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X - adafruit/Adafruit LPS2X@2.0.6 - # renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library - adafruit/Adafruit SHT31 Library@2.2.2 - # renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library - adafruit/Adafruit VEML7700 Library@2.1.6 - # renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library - adafruit/Adafruit SHT4x Library@1.0.5 - # renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library - sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6 - # renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001 - closedcube/ClosedCube OPT3001@1.1.2 - # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master - https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip - # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core - sensirion/Sensirion Core@0.7.3 - # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x - sensirion/Sensirion I2C SCD4x@1.1.0 + ${environmental_extra_common.lib_deps} + # renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680 + adafruit/Adafruit BME680 Library@^2.0.5 diff --git a/protobufs b/protobufs index e1a6b3a86..44298d374 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit e1a6b3a868d735da72cd6c94c574d655129d390a +Subproject commit 44298d374fd83cfbc36fdb76c6f966e980cadd93 diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h index 947b1e054..d52b10a53 100644 --- a/src/AmbientLightingThread.h +++ b/src/AmbientLightingThread.h @@ -1,19 +1,23 @@ +#ifndef AMBIENTLIGHTINGTHREAD_H +#define AMBIENTLIGHTINGTHREAD_H + #include "Observer.h" #include "configuration.h" +#include "detect/ScanI2C.h" +#include "sleep.h" #ifdef HAS_NCP5623 -#include -NCP5623 rgb; +#include + +#include #endif #ifdef HAS_LP5562 #include -LP5562 rgbw; #endif #ifdef HAS_NEOPIXEL -#include -Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE); +#include #endif #ifdef UNPHONE @@ -21,10 +25,24 @@ Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE); extern unPhone unphone; #endif -namespace concurrency -{ class AmbientLightingThread : public concurrency::OSThread { + friend class StatusLEDModule; // Let the LEDStatusModule trigger the ambient lighting for notifications and battery status. + friend class ExternalNotificationModule; // Let the ExternalNotificationModule trigger the ambient lighting for notifications. + + private: +#ifdef HAS_NCP5623 + NCP5623 rgb; +#endif + +#ifdef HAS_LP5562 + LP5562 rgbw; +#endif + +#ifdef HAS_NEOPIXEL + Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE); +#endif + public: explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLighting") { @@ -36,14 +54,15 @@ class AmbientLightingThread : public concurrency::OSThread moduleConfig.ambient_lighting.led_state = true; #endif #endif - // Uncomment to test module - // moduleConfig.ambient_lighting.led_state = true; - // moduleConfig.ambient_lighting.current = 10; +#if AMBIENT_LIGHTING_TEST + // define to enable test + moduleConfig.ambient_lighting.led_state = true; + moduleConfig.ambient_lighting.current = 10; // Default to a color based on our node number - // moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16; - // moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; - // moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; - + moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16; + moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8; + moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF; +#endif #if defined(HAS_NCP5623) || defined(HAS_LP5562) _type = type; if (_type == ScanI2C::DeviceType::NONE) { @@ -53,11 +72,6 @@ class AmbientLightingThread : public concurrency::OSThread } #endif #ifdef HAS_RGB_LED - if (!moduleConfig.ambient_lighting.led_state) { - LOG_DEBUG("AmbientLighting Disable due to moduleConfig.ambient_lighting.led_state OFF"); - disable(); - return; - } LOG_DEBUG("AmbientLighting init"); #ifdef HAS_NCP5623 if (_type == ScanI2C::NCP5623) { @@ -77,7 +91,13 @@ class AmbientLightingThread : public concurrency::OSThread pixels.clear(); // Set all pixel colors to 'off' pixels.setBrightness(moduleConfig.ambient_lighting.current); #endif - setLighting(); + if (!moduleConfig.ambient_lighting.led_state) { + LOG_DEBUG("AmbientLighting Disable due to moduleConfig.ambient_lighting.led_state OFF"); + disable(); + return; + } + setLighting(moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, + moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); #endif #if defined(HAS_NCP5623) || defined(HAS_LP5562) } @@ -91,7 +111,8 @@ class AmbientLightingThread : public concurrency::OSThread #if defined(HAS_NCP5623) || defined(HAS_LP5562) if ((_type == ScanI2C::NCP5623 || _type == ScanI2C::LP5562) && moduleConfig.ambient_lighting.led_state) { #endif - setLighting(); + setLighting(moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, + moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification #if defined(HAS_NCP5623) || defined(HAS_LP5562) } @@ -148,65 +169,53 @@ class AmbientLightingThread : public concurrency::OSThread return 0; } - void setLighting() + protected: + void setLighting(float current, uint8_t red, uint8_t green, uint8_t blue) { #ifdef HAS_NCP5623 - rgb.setCurrent(moduleConfig.ambient_lighting.current); - rgb.setRed(moduleConfig.ambient_lighting.red); - rgb.setGreen(moduleConfig.ambient_lighting.green); - rgb.setBlue(moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init NCP5623 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", - moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + rgb.setCurrent(current); + rgb.setRed(red); + rgb.setGreen(green); + rgb.setBlue(blue); + LOG_DEBUG("Init NCP5623 Ambient light w/ current=%f, red=%d, green=%d, blue=%d", current, red, green, blue); #endif #ifdef HAS_LP5562 - rgbw.setCurrent(moduleConfig.ambient_lighting.current); - rgbw.setRed(moduleConfig.ambient_lighting.red); - rgbw.setGreen(moduleConfig.ambient_lighting.green); - rgbw.setBlue(moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init LP5562 Ambient light w/ current=%d, red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.current, - moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + rgbw.setCurrent(current); + rgbw.setRed(red); + rgbw.setGreen(green); + rgbw.setBlue(blue); + LOG_DEBUG("Init LP5562 Ambient light w/ current=%f, red=%d, green=%d, blue=%d", current, red, green, blue); #endif #ifdef HAS_NEOPIXEL - pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, - moduleConfig.ambient_lighting.blue), - 0, NEOPIXEL_COUNT); + pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); // RadioMaster Bandit has addressable LED at the two buttons // this allow us to set different lighting for them in variant.h file. -#ifdef RADIOMASTER_900_BANDIT #if defined(BUTTON1_COLOR) && defined(BUTTON1_COLOR_INDEX) pixels.fill(BUTTON1_COLOR, BUTTON1_COLOR_INDEX, 1); #endif #if defined(BUTTON2_COLOR) && defined(BUTTON2_COLOR_INDEX) pixels.fill(BUTTON2_COLOR, BUTTON2_COLOR_INDEX, 1); -#endif #endif pixels.show(); - // LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d", - // moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, - // moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + // LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%f, red=%d, green=%d, blue=%d", + // current, red, green, blue); #endif #ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red); - analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green); - analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + analogWrite(RGBLED_RED, 255 - red); + analogWrite(RGBLED_GREEN, 255 - green); + analogWrite(RGBLED_BLUE, 255 - blue); + LOG_DEBUG("Init Ambient light RGB Common Anode w/ red=%d, green=%d, blue=%d", red, green, blue); #elif defined(RGBLED_RED) - analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red); - analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green); - analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init Ambient light RGB Common Cathode w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + analogWrite(RGBLED_RED, red); + analogWrite(RGBLED_GREEN, green); + analogWrite(RGBLED_BLUE, blue); + LOG_DEBUG("Init Ambient light RGB Common Cathode w/ red=%d, green=%d, blue=%d", red, green, blue); #endif #ifdef UNPHONE - unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, - moduleConfig.ambient_lighting.blue); - LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red, - moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue); + unphone.rgb(red, green, blue); + LOG_DEBUG("Init unPhone Ambient light w/ red=%d, green=%d, blue=%d", red, green, blue); #endif } }; - -} // namespace concurrency +#endif // AMBIENTLIGHTINGTHREAD_H \ No newline at end of file diff --git a/src/AudioThread.h b/src/AudioThread.h index 23552c421..e1ba422bc 100644 --- a/src/AudioThread.h +++ b/src/AudioThread.h @@ -4,6 +4,7 @@ #include "configuration.h" #include "main.h" #include "sleep.h" +#include #ifdef HAS_I2S #include @@ -29,9 +30,9 @@ class AudioThread : public concurrency::OSThread io.digitalWrite(EXPANDS_AMP_EN, HIGH); #endif setCPUFast(true); - rtttlFile = new AudioFileSourcePROGMEM(data, len); - i2sRtttl = new AudioGeneratorRTTTL(); - i2sRtttl->begin(rtttlFile, audioOut); + rtttlFile = std::unique_ptr(new AudioFileSourcePROGMEM(data, len)); + i2sRtttl = std::unique_ptr(new AudioGeneratorRTTTL()); + i2sRtttl->begin(rtttlFile.get(), audioOut.get()); } // Also handles actually playing the RTTTL, needs to be called in loop @@ -47,12 +48,10 @@ class AudioThread : public concurrency::OSThread { if (i2sRtttl != nullptr) { i2sRtttl->stop(); - delete i2sRtttl; i2sRtttl = nullptr; } if (rtttlFile != nullptr) { - delete rtttlFile; rtttlFile = nullptr; } @@ -66,16 +65,14 @@ class AudioThread : public concurrency::OSThread { if (i2sRtttl != nullptr) { i2sRtttl->stop(); - delete i2sRtttl; i2sRtttl = nullptr; } #ifdef T_LORA_PAGER io.digitalWrite(EXPANDS_AMP_EN, HIGH); #endif - ESP8266SAM *sam = new ESP8266SAM; - sam->Say(audioOut, text); - delete sam; + auto sam = std::unique_ptr(new ESP8266SAM); + sam->Say(audioOut.get(), text); setCPUFast(false); #ifdef T_LORA_PAGER io.digitalWrite(EXPANDS_AMP_EN, LOW); @@ -96,15 +93,15 @@ class AudioThread : public concurrency::OSThread private: void initOutput() { - audioOut = new AudioOutputI2S(1, AudioOutputI2S::EXTERNAL_I2S); + audioOut = std::unique_ptr(new AudioOutputI2S(1, AudioOutputI2S::EXTERNAL_I2S)); audioOut->SetPinout(DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_MCLK); audioOut->SetGain(0.2); }; - AudioGeneratorRTTTL *i2sRtttl = nullptr; - AudioOutputI2S *audioOut = nullptr; + std::unique_ptr i2sRtttl = nullptr; + std::unique_ptr audioOut = nullptr; - AudioFileSourcePROGMEM *rtttlFile = nullptr; + std::unique_ptr rtttlFile = nullptr; }; #endif diff --git a/src/BluetoothStatus.h b/src/BluetoothStatus.h index 680aec929..4ea4a95ac 100644 --- a/src/BluetoothStatus.h +++ b/src/BluetoothStatus.h @@ -89,22 +89,14 @@ class BluetoothStatus : public Status case ConnectionState::CONNECTED: LOG_DEBUG("BluetoothStatus CONNECTED"); #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, LOW); -#else - digitalWrite(BLE_LED, HIGH); -#endif + digitalWrite(BLE_LED, LED_STATE_ON); #endif break; case ConnectionState::DISCONNECTED: LOG_DEBUG("BluetoothStatus DISCONNECTED"); #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif break; } diff --git a/src/Led.cpp b/src/Led.cpp deleted file mode 100644 index 6406cd2f7..000000000 --- a/src/Led.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "Led.h" -#include "PowerMon.h" -#include "main.h" -#include "power.h" - -GpioVirtPin ledForceOn, ledBlink; - -#if defined(LED_PIN) -// Most boards have a GPIO for LED control -static GpioHwPin ledRawHwPin(LED_PIN); -#else -static GpioVirtPin ledRawHwPin; // Dummy pin for no hardware -#endif - -#if LED_STATE_ON == 0 -static GpioVirtPin ledHwPin; -static GpioNotTransformer ledInverter(&ledHwPin, &ledRawHwPin); -#else -static GpioPin &ledHwPin = ledRawHwPin; -#endif - -#if defined(HAS_PMU) -/** - * A GPIO controlled by the PMU - */ -class GpioPmuPin : public GpioPin -{ - public: - void set(bool value) - { - if (pmu_found && PMU) { - // blink the axp led - PMU->setChargingLedMode(value ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); - } - } -} ledPmuHwPin; - -// In some cases we need to drive a PMU LED and a normal LED -static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin); -#else -static GpioPin &ledFinalPin = ledHwPin; -#endif - -#ifdef USE_POWERMON -/** - * We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff. - */ -class MonitoredLedPin : public GpioPin -{ - public: - void set(bool value) - { - if (powerMon) { - if (value) - powerMon->setState(meshtastic_PowerMon_State_LED_On); - else - powerMon->clearState(meshtastic_PowerMon_State_LED_On); - } - ledFinalPin.set(value); - } -} monitoredLedPin; -#else -static GpioPin &monitoredLedPin = ledFinalPin; -#endif - -static GpioBinaryTransformer ledForcer(&ledForceOn, &ledBlink, &monitoredLedPin, GpioBinaryTransformer::Or); \ No newline at end of file diff --git a/src/Led.h b/src/Led.h deleted file mode 100644 index 68833e041..000000000 --- a/src/Led.h +++ /dev/null @@ -1,7 +0,0 @@ -#include "GpioLogic.h" -#include "configuration.h" - -/** - * ledForceOn and ledForceOff both override the normal ledBlinker behavior (which is controlled by main) - */ -extern GpioVirtPin ledForceOn, ledBlink; \ No newline at end of file diff --git a/src/Power.cpp b/src/Power.cpp index b211d760e..ea4fcf42a 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -459,6 +459,8 @@ class AnalogBatteryLevel : public HasBatteryLevel } // if it's not HIGH - check the battery #endif + // If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it. + return false; // technically speaking this should work for all(?) NRF52 boards // but needs testing across multiple devices. NRF52 USB would not even work if @@ -690,7 +692,9 @@ bool Power::setup() bool found = false; if (axpChipInit()) { found = true; - } else if (lipoInit()) { + } else if (cw2015Init()) { + found = true; + } else if (max17048Init()) { found = true; } else if (lipoChargerInit()) { found = true; @@ -700,11 +704,11 @@ bool Power::setup() found = true; } else if (analogInit()) { found = true; - } - + } else { #ifdef NRF_APM - found = true; + found = true; #endif + } #ifdef EXT_PWR_DETECT attachInterrupt( EXT_PWR_DETECT, @@ -842,8 +846,10 @@ void Power::readPowerStatus() if (batteryLevel) { hasBattery = batteryLevel->isBatteryConnect() ? OptTrue : OptFalse; +#ifndef NRF_APM usbPowered = batteryLevel->isVbusIn() ? OptTrue : OptFalse; isChargingNow = batteryLevel->isCharging() ? OptTrue : OptFalse; +#endif if (hasBattery) { batteryVoltageMv = batteryLevel->getBattVoltage(); // If the AXP192 returns a valid battery percentage, use it @@ -1319,7 +1325,7 @@ bool Power::axpChipInit() /** * Wrapper class for an I2C MAX17048 Lipo battery sensor. */ -class LipoBatteryLevel : public HasBatteryLevel +class MAX17048BatteryLevel : public HasBatteryLevel { private: MAX17048Singleton *max17048 = nullptr; @@ -1367,18 +1373,18 @@ class LipoBatteryLevel : public HasBatteryLevel virtual bool isCharging() override { return max17048->isBatteryCharging(); } }; -LipoBatteryLevel lipoLevel; +MAX17048BatteryLevel max17048Level; /** * Init the Lipo battery level sensor */ -bool Power::lipoInit() +bool Power::max17048Init() { - bool result = lipoLevel.runOnce(); - LOG_DEBUG("Power::lipoInit lipo sensor is %s", result ? "ready" : "not ready yet"); + bool result = max17048Level.runOnce(); + LOG_DEBUG("Power::max17048Init lipo sensor is %s", result ? "ready" : "not ready yet"); if (!result) return false; - batteryLevel = &lipoLevel; + batteryLevel = &max17048Level; return true; } @@ -1386,7 +1392,88 @@ bool Power::lipoInit() /** * The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel */ -bool Power::lipoInit() +bool Power::max17048Init() +{ + return false; +} +#endif + +#if !MESHTASTIC_EXCLUDE_I2C && HAS_CW2015 + +class CW2015BatteryLevel : public AnalogBatteryLevel +{ + public: + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override + { + int data = -1; + Wire.beginTransmission(CW2015_ADDR); + Wire.write(0x04); + if (Wire.endTransmission() == 0) { + if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) { + data = Wire.read(); + } + } + return data; + } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override + { + uint16_t mv = 0; + Wire.beginTransmission(CW2015_ADDR); + Wire.write(0x02); + if (Wire.endTransmission() == 0) { + if (Wire.requestFrom(CW2015_ADDR, (uint8_t)2)) { + mv = Wire.read(); + mv <<= 8; + mv |= Wire.read(); + // Voltage is read in 305uV units, convert to mV + mv = mv * 305 / 1000; + } + } + return mv; + } +}; + +CW2015BatteryLevel cw2015Level; + +/** + * Init the CW2015 battery level sensor + */ +bool Power::cw2015Init() +{ + + Wire.beginTransmission(CW2015_ADDR); + uint8_t getInfo[] = {0x0a, 0x00}; + Wire.write(getInfo, 2); + Wire.endTransmission(); + delay(10); + Wire.beginTransmission(CW2015_ADDR); + Wire.write(0x00); + bool result = false; + if (Wire.endTransmission() == 0) { + if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) { + uint8_t data = Wire.read(); + LOG_DEBUG("CW2015 init read data: 0x%x", data); + if (data == 0x73) { + result = true; + batteryLevel = &cw2015Level; + } + } + } + return result; +} + +#else +/** + * The CW2015 battery level sensor is unavailable - default to AnalogBatteryLevel + */ +bool Power::cw2015Init() { return false; } diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 9f8097b84..2153dbfd5 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -9,13 +9,13 @@ */ #include "PowerFSM.h" #include "Default.h" -#include "Led.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerMon.h" #include "configuration.h" #include "graphics/Screen.h" #include "main.h" +#include "modules/StatusLEDModule.h" #include "sleep.h" #include "target_specific.h" @@ -103,7 +103,7 @@ static void lsIdle() uint32_t sleepTime = SLEEP_TIME; powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep); - ledBlink.set(false); // Never leave led on while in light sleep + statusLEDModule->setPowerLED(false); esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL); powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep); @@ -111,7 +111,7 @@ static void lsIdle() case ESP_SLEEP_WAKEUP_TIMER: // Normal case: timer expired, we should just go back to sleep ASAP - ledBlink.set(true); // briefly turn on led + statusLEDModule->setPowerLED(true); wakeCause2 = doLightSleep(100); // leave led on for 1ms secsSlept += sleepTime; @@ -146,7 +146,7 @@ static void lsIdle() } } else { // Time to stop sleeping! - ledBlink.set(false); + statusLEDModule->setPowerLED(false); LOG_INFO("Reached ls_secs, service loop()"); powerFSM.trigger(EVENT_WAKE_TIMER); } diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index e15d56912..672c8334c 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -227,34 +227,21 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_ isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected(); #endif if (isBleConnected) { - char *message; - size_t initialLen; - size_t len; - initialLen = strlen(format); - message = new char[initialLen + 1]; - len = vsnprintf(message, initialLen + 1, format, arg); - if (len > initialLen) { - delete[] message; - message = new char[len + 1]; - vsnprintf(message, len + 1, format, arg); - } auto thread = concurrency::OSThread::currentThread; meshtastic_LogRecord logRecord = meshtastic_LogRecord_init_zero; logRecord.level = getLogLevel(logLevel); - strcpy(logRecord.message, message); + vsprintf(logRecord.message, format, arg); if (thread) strcpy(logRecord.source, thread->ThreadName.c_str()); logRecord.time = getValidTime(RTCQuality::RTCQualityDevice, true); - uint8_t *buffer = new uint8_t[meshtastic_LogRecord_size]; - size_t size = pb_encode_to_bytes(buffer, meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord); + auto buffer = std::unique_ptr(new uint8_t[meshtastic_LogRecord_size]); + size_t size = pb_encode_to_bytes(buffer.get(), meshtastic_LogRecord_size, meshtastic_LogRecord_fields, &logRecord); #ifdef ARCH_ESP32 - nimbleBluetooth->sendLog(buffer, size); + nimbleBluetooth->sendLog(buffer.get(), size); #elif defined(ARCH_NRF52) - nrf52Bluetooth->sendLog(buffer, size); + nrf52Bluetooth->sendLog(buffer.get(), size); #endif - delete[] message; - delete[] buffer; } } #else @@ -292,8 +279,8 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) // append \n to format size_t len = strlen(format); - char *newFormat = new char[len + 2]; - strcpy(newFormat, format); + auto newFormat = std::unique_ptr(new char[len + 2]); + strcpy(newFormat.get(), format); newFormat[len] = '\n'; newFormat[len + 1] = '\0'; @@ -310,23 +297,18 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) va_end(arg); } if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { - delete[] newFormat; return; } } if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { - delete[] newFormat; return; } else if (portduino_config.logoutputlevel < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) { - delete[] newFormat; return; } else if (portduino_config.logoutputlevel < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) { - delete[] newFormat; return; } #endif if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { - delete[] newFormat; return; } @@ -338,11 +320,19 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) #endif va_list arg; + va_list arg_copy; + va_start(arg, format); - log_to_serial(logLevel, newFormat, arg); - log_to_syslog(logLevel, newFormat, arg); - log_to_ble(logLevel, newFormat, arg); + va_copy(arg_copy, arg); + log_to_serial(logLevel, newFormat.get(), arg_copy); + va_end(arg_copy); + + va_copy(arg_copy, arg); + log_to_syslog(logLevel, newFormat.get(), arg_copy); + va_end(arg_copy); + + log_to_ble(logLevel, newFormat.get(), arg); va_end(arg); #ifdef HAS_FREE_RTOS @@ -352,7 +342,6 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) #endif } - delete[] newFormat; return; } diff --git a/src/configuration.h b/src/configuration.h index 744597b3c..53ae30d51 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -217,6 +217,7 @@ along with this program. If not, see . #define SHTC3_ADDR 0x70 #define LPS22HB_ADDR 0x5C #define LPS22HB_ADDR_ALT 0x5D +#define SFA30_ADDR 0x5D #define SHT31_4x_ADDR 0x44 #define SHT31_4x_ADDR_ALT 0x45 #define PMSA003I_ADDR 0x12 @@ -233,6 +234,7 @@ along with this program. If not, see . #define NAU7802_ADDR 0x2A #define MAX30102_ADDR 0x57 #define SCD4X_ADDR 0x62 +#define CW2015_ADDR 0x62 #define MLX90614_ADDR_DEF 0x5A #define CGRADSENS_ADDR 0x66 #define LTR390UV_ADDR 0x53 @@ -242,6 +244,7 @@ along with this program. If not, see . #define BQ25896_ADDR 0x6B #define LTR553ALS_ADDR 0x23 #define SEN5X_ADDR 0x69 +#define SCD30_ADDR 0x61 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index bf01a0365..3389cce91 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -43,8 +43,8 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const ScanI2C::FoundDevice ScanI2C::firstAQI() const { - ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X}; - return firstOfOrNONE(2, types); + ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X, SFA30}; + return firstOfOrNONE(4, types); } ScanI2C::FoundDevice ScanI2C::firstRGBLED() const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 3910ddf64..7b575dd63 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -89,7 +89,10 @@ class ScanI2C DA217, CHSC6X, CST226SE, - SEN5X + SEN5X, + SFA30, + CW2015, + SCD30 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 6655addf1..c58a58518 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -1,4 +1,6 @@ #include "ScanI2CTwoWire.h" +#include "configuration.h" +#include "detect/ScanI2C.h" #if !MESHTASTIC_EXCLUDE_I2C @@ -115,6 +117,25 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation return value; } +bool ScanI2CTwoWire::i2cCommandResponseLength(ScanI2C::DeviceAddress addr, uint16_t command, uint8_t expectedLength) const +{ + TwoWire *i2cBus = fetchI2CBus(addr); + i2cBus->beginTransmission(addr.address); + if (command > 0xFF) { + i2cBus->write((uint8_t)(command >> 8)); + } + i2cBus->write((uint8_t)(command & 0xFF)); + if (i2cBus->endTransmission() != 0) { + return false; + } + delay(20); + uint8_t received = i2cBus->requestFrom(addr.address, expectedLength); + bool match = (received == expectedLength); + while (i2cBus->available()) + i2cBus->read(); + return match; +} + /// for SEN5X detection // Note, this code needs to be called before setting the I2C bus speed // for the screen at high speed. The speed needs to be at 100kHz, otherwise @@ -430,8 +451,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) { type = OPT3001; logFoundDevice("OPT3001", (uint8_t)addr.address); - } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 6) != - 0) { // unique SHT4x serial number (6 bytes inc. CRC) + } else if (i2cCommandResponseLength(addr, 0x89, 6)) { // SHT4x serial number (6 bytes inc. CRC) type = SHT4X; logFoundDevice("SHT4X", (uint8_t)addr.address); } else { @@ -456,6 +476,19 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; case LPS22HB_ADDR_ALT: + // SFA30 detection: send 2-byte command 0xD060 (Get Device Marking) and check for 48-byte response + if (i2cCommandResponseLength(addr, 0xD060, 48)) { + type = SFA30; + logFoundDevice("SFA30", (uint8_t)addr.address); + break; + } + // Fallback: LPS22HB detection at alternate address using WHO_AM_I register (0x0F == 0xB1) + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); + if (registerValue == 0xB1) { + type = LPS22HB; + logFoundDevice("LPS22HB", (uint8_t)addr.address); + } + break; SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address) SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address) @@ -548,6 +581,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); + SCAN_SIMPLE_CASE(SCD30_ADDR, SCD30, "SCD30", (uint8_t)addr.address); case CST328_ADDR: // Do we have the CST328 or the CST226SE registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1); @@ -581,7 +615,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) break; SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address); - SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address); + case SCD4X_ADDR: { + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x8), 1); + if (registerValue == 0x18) { + logFoundDevice("CW2015", (uint8_t)addr.address); + type = CW2015; + } else { + logFoundDevice("SCD4X", (uint8_t)addr.address); + type = SCD4X; + } + break; + } SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address); #ifdef HAS_TPS65233 SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address); diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h index c5b791920..841a8b946 100644 --- a/src/detect/ScanI2CTwoWire.h +++ b/src/detect/ScanI2CTwoWire.h @@ -55,6 +55,8 @@ class ScanI2CTwoWire : public ScanI2C uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth, bool) const; + bool i2cCommandResponseLength(DeviceAddress addr, uint16_t command, uint8_t expectedLength) const; + DeviceType probeOLED(ScanI2C::DeviceAddress) const; static void logFoundDevice(const char *device, uint8_t address); diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 13e5c32d1..2beaeb127 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -52,7 +52,7 @@ SerialUART *GPS::_serial_gps = &GPS_SERIAL_PORT; HardwareSerial *GPS::_serial_gps = nullptr; #endif -GPS *gps = nullptr; +std::unique_ptr gps = nullptr; static GPSUpdateScheduling scheduling; @@ -127,7 +127,7 @@ static int32_t gpsSwitch() return 1000; } -static concurrency::Periodic *gpsPeriodic; +static std::unique_ptr gpsPeriodic; #endif static void UBXChecksum(uint8_t *message, size_t length) @@ -1485,7 +1485,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector 2048) bufferSize = 2048; - char *response = new char[bufferSize](); // Dynamically allocate based on baud rate + auto response = std::unique_ptr(new char[bufferSize]); // Dynamically allocate based on baud rate uint16_t responseLen = 0; unsigned long start = millis(); while (millis() - start < timeout) { @@ -1501,19 +1501,18 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) { // check if we can see our chips for (const auto &chipInfo : responseMap) { - if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) { + if (strstr(response.get(), chipInfo.detectionString.c_str()) != nullptr) { #ifdef GPS_DEBUG - LOG_DEBUG(response); + LOG_DEBUG(response.get()); #endif LOG_INFO("%s detected", chipInfo.chipName.c_str()); - delete[] response; // Cleanup before return return chipInfo.driver; } } } if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') { #ifdef GPS_DEBUG - LOG_DEBUG(response); + LOG_DEBUG(response.get()); #endif // Reset the response buffer for the next potential message responseLen = 0; @@ -1522,13 +1521,12 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector GPS::createGps() { int8_t _rx_gpio = config.position.rx_gpio; int8_t _tx_gpio = config.position.tx_gpio; @@ -1553,7 +1551,7 @@ GPS *GPS::createGps() if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all return nullptr; - GPS *new_gps = new GPS; + auto new_gps = std::unique_ptr(new GPS()); new_gps->rx_gpio = _rx_gpio; new_gps->tx_gpio = _tx_gpio; @@ -1581,7 +1579,7 @@ GPS *GPS::createGps() #ifdef PIN_GPS_SWITCH // toggle GPS via external GPIO switch pinMode(PIN_GPS_SWITCH, INPUT); - gpsPeriodic = new concurrency::Periodic("GPSSwitch", gpsSwitch); + gpsPeriodic = std::unique_ptr(new concurrency::Periodic("GPSSwitch", gpsSwitch)); #endif // Currently disabled per issue #525 (TinyGPS++ crash bug) diff --git a/src/gps/GPS.h b/src/gps/GPS.h index fcbf361d5..8d63ce82f 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -2,6 +2,8 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_GPS +#include + #include "GPSStatus.h" #include "GpioLogic.h" #include "Observer.h" @@ -118,7 +120,7 @@ class GPS : private concurrency::OSThread // Creates an instance of the GPS class. // Returns the new instance or null if the GPS is not present. - static GPS *createGps(); + static std::unique_ptr createGps(); // Wake the GPS hardware - ready for an update void up(); @@ -256,5 +258,5 @@ class GPS : private concurrency::OSThread uint8_t fixeddelayCtr = 0; }; -extern GPS *gps; +extern std::unique_ptr gps; #endif // Exclude GPS diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 5e31de950..e67bef53e 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -312,7 +312,7 @@ const char *RtcName(RTCQuality quality) * @param t The time to potentially set the RTC to. * @return True if the RTC was set to the provided time, false otherwise. */ -RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) +RTCSetResult perhapsSetRTC(RTCQuality q, const struct tm &t) { /* Convert to unix time The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 diff --git a/src/gps/RTC.h b/src/gps/RTC.h index cf6db0239..16ecd8245 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -41,7 +41,7 @@ extern uint32_t lastSetFromPhoneNtpOrGps; /// If we haven't yet set our RTC this boot, set it from a GPS derived time RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false); -RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t); +RTCSetResult perhapsSetRTC(RTCQuality q, const struct tm &t); /// Return a string name for the quality const char *RtcName(RTCQuality quality); diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp index 8e4adf87e..892a4a885 100644 --- a/src/graphics/EInkDynamicDisplay.cpp +++ b/src/graphics/EInkDynamicDisplay.cpp @@ -10,7 +10,7 @@ EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDI { // If tracking ghost pixels, grab memory #ifdef EINK_LIMIT_GHOSTING_PX - dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros + dirtyPixels = std::unique_ptr(new uint8_t[EInkDisplay::displayBufferSize]()); // Init with zeros #endif } @@ -19,7 +19,7 @@ EInkDynamicDisplay::~EInkDynamicDisplay() { // If we were tracking ghost pixels, free the memory #ifdef EINK_LIMIT_GHOSTING_PX - delete[] dirtyPixels; + dirtyPixels = nullptr; #endif } @@ -454,7 +454,7 @@ void EInkDynamicDisplay::checkExcessiveGhosting() void EInkDynamicDisplay::resetGhostPixelTracking() { // Copy the current frame into dirtyPixels[] from the display buffer - memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize); + memcpy(dirtyPixels.get(), EInkDisplay::buffer, EInkDisplay::displayBufferSize); } #endif // EINK_LIMIT_GHOSTING_PX diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h index d5e29e3f0..b03061873 100644 --- a/src/graphics/EInkDynamicDisplay.h +++ b/src/graphics/EInkDynamicDisplay.h @@ -1,6 +1,7 @@ #pragma once #include "configuration.h" +#include #if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY) @@ -116,11 +117,11 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo // Optional - track ghosting, pixel by pixel // May 2024: no longer used by any display. Kept for possible future use. #ifdef EINK_LIMIT_GHOSTING_PX - void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh - void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit - void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display. - uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) - uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use + void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh + void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit + void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display. + std::unique_ptr dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem) + uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use #endif // Conditional - async full refresh - only with modified meshtastic/GxEPD2 diff --git a/src/graphics/NeoPixel.h b/src/graphics/NeoPixel.h deleted file mode 100644 index dde74366e..000000000 --- a/src/graphics/NeoPixel.h +++ /dev/null @@ -1,4 +0,0 @@ -#ifdef HAS_NEOPIXEL -#include -extern Adafruit_NeoPixel pixels; -#endif \ No newline at end of file diff --git a/src/graphics/NomadStarLED.h b/src/graphics/NomadStarLED.h index 0633a577e..6633db0c8 100644 --- a/src/graphics/NomadStarLED.h +++ b/src/graphics/NomadStarLED.h @@ -1,4 +1,6 @@ #ifdef HAS_LP5562 +#include + #include extern LP5562 rgbw; diff --git a/src/graphics/RAKled.h b/src/graphics/RAKled.h deleted file mode 100644 index 659ea9b72..000000000 --- a/src/graphics/RAKled.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifdef HAS_NCP5623 -#include -extern NCP5623 rgb; - -#endif \ No newline at end of file diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp index 8f06fcf9f..b86f3e32c 100644 --- a/src/graphics/SharedUIDisplay.cpp +++ b/src/graphics/SharedUIDisplay.cpp @@ -221,7 +221,6 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti if (rtc_sec > 0) { // === Build Time String === - long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY; int hour, minute, second; graphics::decomposeTime(rtc_sec, hour, minute, second); snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute); @@ -428,39 +427,33 @@ const int *getTextPositions(OLEDDisplay *display) // ************************* void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y) { - bool drawConnectionState = false; - if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI || - service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET || - service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) { - drawConnectionState = true; - } + if (!isAPIConnected(service->api_state)) + return; - if (drawConnectionState) { - const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1; - display->setColor(BLACK); - display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale), - (connection_icon_height * scale) + (2 * scale)); - display->setColor(WHITE); - if (currentResolution == ScreenResolution::High) { - const int bytesPerRow = (connection_icon_width + 7) / 8; - int iconX = 0; - int iconY = SCREEN_HEIGHT - (connection_icon_height * 2); + const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1; + display->setColor(BLACK); + display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale), + (connection_icon_height * scale) + (2 * scale)); + display->setColor(WHITE); + if (currentResolution == ScreenResolution::High) { + const int bytesPerRow = (connection_icon_width + 7) / 8; + int iconX = 0; + int iconY = SCREEN_HEIGHT - (connection_icon_height * 2); - for (int yy = 0; yy < connection_icon_height; ++yy) { - const uint8_t *rowPtr = connection_icon + yy * bytesPerRow; - for (int xx = 0; xx < connection_icon_width; ++xx) { - const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3)); - const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first - if (byteVal & bitMask) { - display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale); - } + for (int yy = 0; yy < connection_icon_height; ++yy) { + const uint8_t *rowPtr = connection_icon + yy * bytesPerRow; + for (int xx = 0; xx < connection_icon_width; ++xx) { + const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3)); + const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first + if (byteVal & bitMask) { + display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale); } } - - } else { - display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height, - connection_icon); } + + } else { + display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height, + connection_icon); } } diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h index a8ecdfada..35e767056 100644 --- a/src/graphics/SharedUIDisplay.h +++ b/src/graphics/SharedUIDisplay.h @@ -63,4 +63,18 @@ bool isAllowedPunctuation(char c); std::string sanitizeString(const std::string &input); +static inline bool isAPIConnected(uint8_t state) +{ + static constexpr bool connectedStates[] = { + /* STATE_NONE */ false, + /* STATE_BLE */ true, + /* STATE_WIFI */ true, + /* STATE_SERIAL */ true, + /* STATE_PACKET */ true, + /* STATE_HTTP */ true, + /* STATE_ETH */ true, + }; + return state < sizeof(connectedStates) ? connectedStates[state] : false; +} + } // namespace graphics diff --git a/src/graphics/TimeFormatters.cpp b/src/graphics/TimeFormatters.cpp index 0a1c23341..02450efa3 100644 --- a/src/graphics/TimeFormatters.cpp +++ b/src/graphics/TimeFormatters.cpp @@ -110,14 +110,14 @@ void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, ui uint32_t secs = (uptimeMillis % 60000) / 1000; if (days) { - snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours); + snprintf(uptimeStr, maxLength, "%s%ud %uh", prefix, days, hours); } else if (hours) { - snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins); + snprintf(uptimeStr, maxLength, "%s%uh %um", prefix, hours, mins); } else if (!includeSecs) { - snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins); + snprintf(uptimeStr, maxLength, "%s%um", prefix, mins); } else if (mins) { - snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs); + snprintf(uptimeStr, maxLength, "%s%um %us", prefix, mins, secs); } else { - snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs); + snprintf(uptimeStr, maxLength, "%s%us", prefix, secs); } -} +} \ No newline at end of file diff --git a/src/graphics/VirtualKeyboard.cpp b/src/graphics/VirtualKeyboard.cpp index a24f5b15c..52f0195b3 100644 --- a/src/graphics/VirtualKeyboard.cpp +++ b/src/graphics/VirtualKeyboard.cpp @@ -429,6 +429,10 @@ void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool c = c - 'a' + 'A'; } keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c); + // Show the common "/" pairing next to "?" like on a real keyboard + if (key.type == VK_CHAR && key.character == '?') { + keyText = "?/"; + } } int textWidth = display->getStringWidth(keyText.c_str()); @@ -518,9 +522,13 @@ char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress) char c = key.character; - // Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings - if (isLongPress && c >= 'a' && c <= 'z') { - c = (char)(c - 'a' + 'A'); + // Long-press: letters become uppercase; for "?" provide "/" like a typical keyboard + if (isLongPress) { + if (c >= 'a' && c <= 'z') { + c = (char)(c - 'a' + 'A'); + } else if (c == '?') { + c = '/'; + } } return c; diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 2dca38d66..2069c71ec 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -663,7 +663,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it char uptimeStr[32] = ""; - getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr)); + getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr)); textWidth = display->getStringWidth(uptimeStr); nameX = (SCREEN_WIDTH - textWidth) / 2; display->drawString(nameX, getTextPositions(display)[line++], uptimeStr); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 195da09f9..f57c39512 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -539,7 +539,7 @@ void menuHandler::messageResponseMenu() // If viewing ALL chats, hide “Mute Chat” if (mode != graphics::MessageRenderer::ThreadMode::ALL && mode != graphics::MessageRenderer::ThreadMode::DIRECT) { const uint8_t chIndex = (threadChannel != 0) ? (uint8_t)threadChannel : channels.getPrimaryIndex(); - auto &chan = channels.getByIndex(chIndex); + const auto &chan = channels.getByIndex(chIndex); optionsArray[options] = chan.settings.module_settings.is_muted ? "Unmute Channel" : "Mute Channel"; optionsEnumArray[options++] = MuteChannel; @@ -831,7 +831,7 @@ void menuHandler::messageViewModeMenu() // Gather unique peers auto dms = messageStore.getDirectMessages(); std::vector uniquePeers; - for (auto &m : dms) { + for (const auto &m : dms) { uint32_t peer = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender; if (peer != nodeDB->getNodeNum() && std::find(uniquePeers.begin(), uniquePeers.end(), peer) == uniquePeers.end()) uniquePeers.push_back(peer); @@ -1397,7 +1397,7 @@ void menuHandler::manageNodeMenu() } if (selected == Favorite) { - auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum); + const auto *n = nodeDB->getMeshNode(menuHandler::pickedNodeNum); if (!n) { return; } @@ -2292,14 +2292,13 @@ void menuHandler::wifiToggleMenu() void menuHandler::screenOptionsMenu() { // Check if brightness is supported - bool hasSupportBrightness = false; -#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) - hasSupportBrightness = true; -#endif - #if defined(T_DECK) // TDeck Doesn't seem to support brightness at all, at least not reliably - hasSupportBrightness = false; + bool hasSupportBrightness = false; +#elif defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) + bool hasSupportBrightness = true; +#else + bool hasSupportBrightness = false; #endif enum optionsNumbers { Back, Brightness, ScreenColor, FrameToggles, DisplayUnits, MessageBubbles }; @@ -2444,7 +2443,7 @@ void menuHandler::frameTogglesMenu() nodelist_hopsignal, nodelist_distance, nodelist_bearings, - gps, + gps_position, lora, clock, show_favorites, @@ -2482,7 +2481,7 @@ void menuHandler::frameTogglesMenu() #endif optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position"; - optionsEnumArray[options++] = gps; + optionsEnumArray[options++] = gps_position; #endif optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa"; @@ -2545,7 +2544,7 @@ void menuHandler::frameTogglesMenu() screen->toggleFrameVisibility("nodelist_bearings"); menuHandler::menuQueue = menuHandler::FrameToggles; screen->runNow(); - } else if (selected == gps) { + } else if (selected == gps_position) { screen->toggleFrameVisibility("gps"); menuHandler::menuQueue = menuHandler::FrameToggles; screen->runNow(); diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 9d6780130..b36a5057c 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -171,7 +171,7 @@ unsigned long getModeCycleIntervalMs() int calculateMaxScroll(int totalEntries, int visibleRows) { - return std::max(0, (totalEntries - 1) / (visibleRows * 2)); + return max(0, (totalEntries - 1) / (visibleRows * 2)); } void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd) @@ -187,13 +187,12 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries, if (totalEntries <= visibleNodeRows * columns) return; - int scrollbarX = display->getWidth() - 2; int scrollbarHeight = display->getHeight() - scrollStartY - 10; - int thumbHeight = std::max(4, (scrollbarHeight * visibleNodeRows * columns) / totalEntries); - int perPage = visibleNodeRows * columns; - int maxScroll = std::max(0, (totalEntries - 1) / perPage); - int thumbY = scrollStartY + (scrollIndex * (scrollbarHeight - thumbHeight)) / std::max(1, maxScroll); + int thumbHeight = max(4, (scrollbarHeight * visibleNodeRows * columns) / totalEntries); + int thumbY = scrollStartY + (scrollIndex * (scrollbarHeight - thumbHeight)) / + max(1, max(0, (totalEntries - 1) / (visibleNodeRows * columns))); + int scrollbarX = display->getWidth() - 2; for (int i = 0; i < thumbHeight; i++) { display->setPixel(scrollbarX, thumbY + i); } @@ -556,13 +555,13 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t int maxScroll = 0; if (perPage > 0) { - maxScroll = std::max(0, (totalEntries - 1) / perPage); + maxScroll = max(0, (totalEntries - 1) / perPage); } if (scrollIndex > maxScroll) scrollIndex = maxScroll; int startIndex = scrollIndex * visibleNodeRows * totalColumns; - int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries); + int endIndex = min(startIndex + visibleNodeRows * totalColumns, totalEntries); int yOffset = 0; int col = 0; int lastNodeY = y; @@ -580,7 +579,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t if (extras) extras(display, node, xPos, yPos, columnWidth, heading, lat, lon); - lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL); + lastNodeY = max(lastNodeY, yPos + FONT_HEIGHT_SMALL); yOffset += rowYOffset; shownCount++; rowCount++; @@ -613,13 +612,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t if (millis() - popupTime < POPUP_DURATION_MS) { popupTotal = totalEntries; - int perPage = visibleNodeRows * totalColumns; - popupStart = startIndex + 1; - popupEnd = std::min(startIndex + perPage, totalEntries); + popupEnd = min(startIndex + perPage, totalEntries); popupPage = (scrollIndex + 1); - popupMaxPage = std::max(1, (totalEntries + perPage - 1) / perPage); + popupMaxPage = max(1, (totalEntries + perPage - 1) / perPage); char buf[32]; snprintf(buf, sizeof(buf), "%d-%d/%d Pg %d/%d", popupStart, popupEnd, popupTotal, popupPage, popupMaxPage); diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 7ce9d5afe..25a70f16d 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -2,6 +2,7 @@ #if HAS_SCREEN #include "CompassRenderer.h" #include "GPSStatus.h" +#include "MeshService.h" #include "NodeDB.h" #include "NodeListRenderer.h" #include "UIRenderer.h" @@ -287,7 +288,8 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes // ********************** // * Favorite Node Info * // ********************** -void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y) +// cppcheck-suppress constParameterPointer; signature must match FrameCallback typedef from OLEDDisplayUi library +void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { if (favoritedNodes.empty()) return; @@ -313,7 +315,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st // === Create the shortName and title string === const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node"; char titlestr[32] = {0}; - snprintf(titlestr, sizeof(titlestr), "Fav: %s", shortName); + snprintf(titlestr, sizeof(titlestr), "*%s*", shortName); // === Draw battery/time/mail header (common across screens) === graphics::drawCommonHeader(display, x, y, titlestr); @@ -342,34 +344,162 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st } // === 2. Signal and Hops (combined on one line, if available) === - // If both are present: "Sig: 97% [2hops]" - // If only one: show only that one char signalHopsStr[32] = ""; bool haveSignal = false; - int percentSignal = clamp((int)((node->snr + 10) * 5), 0, 100); + int bars = 0; - // Always use "Sig" for the label - const char *signalLabel = " Sig"; + // Helper to get SNR limit based on modem preset + auto getSnrLimit = [](meshtastic_Config_LoRaConfig_ModemPreset preset) -> float { + switch (preset) { + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW: + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE: + case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST: + return -6.0f; + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW: + case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST: + return -5.5f; + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW: + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST: + case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: + return -4.5f; + default: + return -6.0f; + } + }; + + // Calculate signal grade using modem preset and SNR only + float snrLimit = getSnrLimit(config.lora.modem_preset); + float snr = node->snr; + + // Determine signal quality label and bars using SNR-only grading + const char *qualityLabel = nullptr; + + if (snr > snrLimit + 10) { + qualityLabel = "Good"; + bars = 4; + } else if (snr > snrLimit + 6) { + qualityLabel = "Good"; + bars = 3; + } else if (snr > snrLimit + 2) { + qualityLabel = "Good"; + bars = 2; + } else if (snr > snrLimit - 4) { + qualityLabel = "Fair"; + bars = 1; + } else { + qualityLabel = "Bad"; + bars = 1; + } + + // Add extra spacing on the left if we have an API connection to account for the common footer icons + const char *leftSideSpacing = + graphics::isAPIConnected(service->api_state) ? (currentResolution == ScreenResolution::High ? " " : " ") : " "; // --- Build the Signal/Hops line --- - // If SNR looks reasonable, show signal - if ((int)((node->snr + 10) * 5) >= 0 && node->snr > -100) { - snprintf(signalHopsStr, sizeof(signalHopsStr), "%s: %d%%", signalLabel, percentSignal); + // Only show signal if we have valid SNR + if (snr > -100 && snr != 0) { + snprintf(signalHopsStr, sizeof(signalHopsStr), "%sSig:%s", leftSideSpacing, qualityLabel); haveSignal = true; } - // If hops is valid (>0), show right after signal + if (node->hops_away > 0) { size_t len = strlen(signalHopsStr); - // Decide between "1 Hop" and "N Hops" if (haveSignal) { - snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [%d %s]", node->hops_away, - (node->hops_away == 1 ? "Hop" : "Hops")); + snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [#]"); } else { - snprintf(signalHopsStr, sizeof(signalHopsStr), "[%d %s]", node->hops_away, (node->hops_away == 1 ? "Hop" : "Hops")); + snprintf(signalHopsStr, sizeof(signalHopsStr), "[#]"); } } - if (signalHopsStr[0] && line < 5) { - display->drawString(x, getTextPositions(display)[line++], signalHopsStr); + + if (signalHopsStr[0]) { + int yPos = getTextPositions(display)[line++]; + int curX = x; + + // Split combined string into signal text and hop suffix + char sigPart[20] = ""; + const char *hopPart = nullptr; + + char *bracket = strchr(signalHopsStr, '['); + if (bracket) { + size_t n = (size_t)(bracket - signalHopsStr); + if (n >= sizeof(sigPart)) + n = sizeof(sigPart) - 1; + memcpy(sigPart, signalHopsStr, n); + sigPart[n] = '\0'; + + // Trim trailing spaces + while (strlen(sigPart) && sigPart[strlen(sigPart) - 1] == ' ') { + sigPart[strlen(sigPart) - 1] = '\0'; + } + + hopPart = bracket; // "[n Hop(s)]" + } else { + strncpy(sigPart, signalHopsStr, sizeof(sigPart) - 1); + sigPart[sizeof(sigPart) - 1] = '\0'; + } + + // Draw signal quality text + display->drawString(curX, yPos, sigPart); + curX += display->getStringWidth(sigPart) + 4; + + // Draw signal bars (skip on UltraLow, text only) + if (currentResolution != ScreenResolution::UltraLow && haveSignal && bars > 0) { + const int kMaxBars = 4; + if (bars < 1) + bars = 1; + if (bars > kMaxBars) + bars = kMaxBars; + + int barX = curX; + + const bool hi = (currentResolution == ScreenResolution::High); + int barWidth = hi ? 2 : 1; + int barGap = hi ? 2 : 1; + int maxBarHeight = FONT_HEIGHT_SMALL - 7; + if (!hi) + maxBarHeight -= 1; + int barY = yPos + (FONT_HEIGHT_SMALL - maxBarHeight) / 2; + + for (int bi = 0; bi < kMaxBars; bi++) { + int barHeight = maxBarHeight * (bi + 1) / kMaxBars; + if (barHeight < 2) + barHeight = 2; + + int bx = barX + bi * (barWidth + barGap); + int by = barY + maxBarHeight - barHeight; + + if (bi < bars) { + display->fillRect(bx, by, barWidth, barHeight); + } else { + int baseY = barY + maxBarHeight - 1; + display->drawHorizontalLine(bx, baseY, barWidth); + } + } + + curX += (kMaxBars * barWidth) + ((kMaxBars - 1) * barGap) + 2; + } + + // Draw hops AFTER the bars as: [ number + hop icon ] + if (hopPart && node->hops_away > 0) { + + // open bracket + display->drawString(curX, yPos, "["); + curX += display->getStringWidth("[") + 1; + + // hop count + char hopCount[6]; + snprintf(hopCount, sizeof(hopCount), "%d", node->hops_away); + display->drawString(curX, yPos, hopCount); + curX += display->getStringWidth(hopCount) + 2; + + // hop icon + const int iconY = yPos + (FONT_HEIGHT_SMALL - hop_height) / 2; + display->drawXbm(curX, iconY, hop_width, hop_height, hop); + curX += hop_width + 1; + + // closing bracket + display->drawString(curX, yPos, "]"); + } } // === 3. Heard (last seen, skip if node never seen) === @@ -377,8 +507,8 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st uint32_t seconds = sinceLastSeen(node); if (seconds != 0 && seconds != UINT32_MAX) { uint32_t minutes = seconds / 60, hours = minutes / 60, days = hours / 24; - // Format as "Heard: Xm ago", "Heard: Xh ago", or "Heard: Xd ago" - snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard: ?" : " Heard: %d%c ago"), + // Format as "Heard:Xm ago", "Heard:Xh ago", or "Heard:Xd ago" + snprintf(seenStr, sizeof(seenStr), (days > 365 ? " Heard:?" : "%sHeard:%d%c ago"), leftSideSpacing, (days ? days : hours ? hours : minutes), @@ -386,16 +516,18 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st : hours ? 'h' : 'm')); } - if (seenStr[0] && line < 5) { + if (seenStr[0]) { display->drawString(x, getTextPositions(display)[line++], seenStr); } #if !defined(M5STACK_UNITC6L) // === 4. Uptime (only show if metric is present) === char uptimeStr[32] = ""; if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) { - getUptimeStr(node->device_metrics.uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr)); + char upPrefix[12]; // enough for leftSideSpacing + "Up:" + snprintf(upPrefix, sizeof(upPrefix), "%sUp:", leftSideSpacing); + getUptimeStr(node->device_metrics.uptime_seconds * 1000, upPrefix, uptimeStr, sizeof(uptimeStr)); } - if (uptimeStr[0] && line < 5) { + if (uptimeStr[0]) { display->drawString(x, getTextPositions(display)[line++], uptimeStr); } @@ -422,16 +554,16 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st if (miles < 0.1) { int feet = (int)(miles * 5280); if (feet > 0 && feet < 1000) { - snprintf(distStr, sizeof(distStr), " Distance: %dft", feet); + snprintf(distStr, sizeof(distStr), "%sDistance:%dft", leftSideSpacing, feet); haveDistance = true; } else if (feet >= 1000) { - snprintf(distStr, sizeof(distStr), " Distance: ¼mi"); + snprintf(distStr, sizeof(distStr), "%sDistance:¼mi", leftSideSpacing); haveDistance = true; } } else { int roundedMiles = (int)(miles + 0.5); if (roundedMiles > 0 && roundedMiles < 1000) { - snprintf(distStr, sizeof(distStr), " Distance: %dmi", roundedMiles); + snprintf(distStr, sizeof(distStr), "%sDistance:%dmi", leftSideSpacing, roundedMiles); haveDistance = true; } } @@ -439,26 +571,74 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st if (distanceKm < 1.0) { int meters = (int)(distanceKm * 1000); if (meters > 0 && meters < 1000) { - snprintf(distStr, sizeof(distStr), " Distance: %dm", meters); + snprintf(distStr, sizeof(distStr), "%sDistance:%dm", leftSideSpacing, meters); haveDistance = true; } else if (meters >= 1000) { - snprintf(distStr, sizeof(distStr), " Distance: 1km"); + snprintf(distStr, sizeof(distStr), "%sDistance:1km", leftSideSpacing); haveDistance = true; } } else { int km = (int)(distanceKm + 0.5); if (km > 0 && km < 1000) { - snprintf(distStr, sizeof(distStr), " Distance: %dkm", km); + snprintf(distStr, sizeof(distStr), "%sDistance:%dkm", leftSideSpacing, km); haveDistance = true; } } } } - // Only display if we actually have a value! - if (haveDistance && distStr[0] && line < 5) { + if (haveDistance && distStr[0]) { display->drawString(x, getTextPositions(display)[line++], distStr); } + // === 6. Battery after Distance line, otherwise next available line === + char batLine[32] = ""; + bool haveBatLine = false; + + if (node->has_device_metrics) { + bool hasPct = node->device_metrics.has_battery_level; + bool hasVolt = node->device_metrics.has_voltage && node->device_metrics.voltage > 0.001f; + + int pct = 0; + float volt = 0.0f; + + if (hasPct) { + pct = (int)node->device_metrics.battery_level; + } + + if (hasVolt) { + volt = node->device_metrics.voltage; + } + + if (hasPct && pct > 0 && pct <= 100) { + // Normal battery percentage + if (hasVolt) { + snprintf(batLine, sizeof(batLine), "%sBat:%d%% (%.2fV)", leftSideSpacing, pct, volt); + } else { + snprintf(batLine, sizeof(batLine), "%sBat:%d%%", leftSideSpacing, pct); + } + haveBatLine = true; + } else if (hasPct && pct > 100) { + // Plugged in + if (hasVolt) { + snprintf(batLine, sizeof(batLine), "%sPlugged In (%.2fV)", leftSideSpacing, volt); + } else { + snprintf(batLine, sizeof(batLine), "%sPlugged In", leftSideSpacing); + } + haveBatLine = true; + } else if (!hasPct && hasVolt) { + // Voltage only + snprintf(batLine, sizeof(batLine), "%sBat:%.2fV", leftSideSpacing, volt); + haveBatLine = true; + } + } + + const int maxTextLines = (currentResolution == ScreenResolution::High) ? 6 : 5; + + // Only draw battery if it fits within the allowed lines + if (haveBatLine && line <= maxTextLines) { + display->drawString(x, getTextPositions(display)[line++], batLine); + } + // --- Compass Rendering: landscape (wide) screens use the original side-aligned logic --- if (SCREEN_WIDTH > SCREEN_HEIGHT) { bool showCompass = false; @@ -593,7 +773,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta } char uptimeStr[32] = ""; if (currentResolution != ScreenResolution::UltraLow) { - getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr)); + getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr)); } display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr); @@ -984,7 +1164,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU config.display.heading_bold = false; const char *displayLine = ""; // Initialize to empty string by default - meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { if (config.position.fixed_position) { @@ -1029,10 +1208,10 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU char uptimeStr[32]; #if defined(USE_EINK) // E-Ink: skip seconds, show only days/hours/mins - getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false); + getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), false); #else // Non E-Ink: include seconds where useful - getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true); + getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), true); #endif display->drawString(0, getTextPositions(display)[line++], uptimeStr); @@ -1210,6 +1389,7 @@ static int8_t lastFrameIndex = -1; static uint32_t lastFrameChangeTime = 0; constexpr uint32_t ICON_DISPLAY_DURATION_MS = 2000; +// cppcheck-suppress constParameterPointer; signature must match OverlayCallback typedef from OLEDDisplayUi library void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state) { int currentFrame = state->currentFrame; diff --git a/src/graphics/draw/UIRenderer.h b/src/graphics/draw/UIRenderer.h index 6e37b68f2..8f0d07881 100644 --- a/src/graphics/draw/UIRenderer.h +++ b/src/graphics/draw/UIRenderer.h @@ -49,7 +49,7 @@ class UIRenderer // Navigation bar overlay static void drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state); - static void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y); + static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); diff --git a/src/graphics/images.h b/src/graphics/images.h index ef9ffef78..66fcbc79c 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -83,6 +83,12 @@ static const unsigned char mail[] PROGMEM = { 0b11111111, 0b00 // Bottom line }; +// Hop icon (9x10) +#define hop_width 9 +#define hop_height 10 +const uint8_t hop[] PROGMEM = {0x05, 0x00, 0x07, 0x00, 0x05, 0x00, 0x38, 0x00, 0x28, 0x00, + 0x38, 0x00, 0xC0, 0x01, 0x40, 0x01, 0xC0, 0x01, 0x40, 0x00}; + // 📬 Mail / Message const uint8_t icon_mail[] PROGMEM = { 0b11111111, // ████████ top border diff --git a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp b/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp index 6d9b709b1..ad92e28ea 100644 --- a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp +++ b/src/graphics/niche/Drivers/Backlight/LatchingBacklight.cpp @@ -42,7 +42,7 @@ int LatchingBacklight::beforeDeepSleep(void *unused) { // Contingency only // - pin wasn't set - if (pin != (uint8_t)-1) { + if (pin != static_cast(-1)) { off(); pinMode(pin, INPUT); // High impedance - unnecessary? } else @@ -55,7 +55,7 @@ int LatchingBacklight::beforeDeepSleep(void *unused) // The effect on the backlight is the same; peek and latch are separated to simplify short vs long press button handling void LatchingBacklight::peek() { - assert(pin != (uint8_t)-1); + assert(pin != static_cast(-1)); digitalWrite(pin, logicActive); // On on = true; latched = false; @@ -67,7 +67,7 @@ void LatchingBacklight::peek() // The effect on the backlight is the same; peek and latch are separated to simplify short vs long press button handling void LatchingBacklight::latch() { - assert(pin != (uint8_t)-1); + assert(pin != static_cast(-1)); // Blink if moving from peek to latch // Indicates to user that the transition has taken place @@ -89,7 +89,7 @@ void LatchingBacklight::latch() // Suitable for ending both peek and latch void LatchingBacklight::off() { - assert(pin != (uint8_t)-1); + assert(pin != static_cast(-1)); digitalWrite(pin, !logicActive); // Off on = false; latched = false; diff --git a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h b/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h index 0097cae4c..87862ea1b 100644 --- a/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h +++ b/src/graphics/niche/Drivers/Backlight/LatchingBacklight.h @@ -40,7 +40,7 @@ class LatchingBacklight CallbackObserver deepSleepObserver = CallbackObserver(this, &LatchingBacklight::beforeDeepSleep); - uint8_t pin = (uint8_t)-1; + uint8_t pin = static_cast(-1); bool logicActive = HIGH; // Is light active HIGH or active LOW bool on = false; // Is light on (either peek or latched) diff --git a/src/graphics/niche/InkHUD/Applet.cpp b/src/graphics/niche/InkHUD/Applet.cpp index ccdd76f97..0a9cd3add 100644 --- a/src/graphics/niche/InkHUD/Applet.cpp +++ b/src/graphics/niche/InkHUD/Applet.cpp @@ -1,3 +1,5 @@ +#include "graphics/niche/InkHUD/Tile.h" +#include #ifdef MESHTASTIC_INCLUDE_INKHUD #include "./Applet.h" @@ -32,7 +34,7 @@ void InkHUD::Applet::drawPixel(int16_t x, int16_t y, uint16_t color) { // Only render pixels if they fall within user's cropped region if (x >= cropLeft && x < (cropLeft + cropWidth) && y >= cropTop && y < (cropTop + cropHeight)) - assignedTile->handleAppletPixel(x, y, (Color)color); + assignedTile->handleAppletPixel(x, y, static_cast(color)); } // Link our applet to a tile @@ -142,6 +144,21 @@ void InkHUD::Applet::resetDrawingSpace() setFont(fontSmall); } +// Sets one or more inputs to enabled/disabled for this applet and if they should be sent to it +void InkHUD::Applet::setInputsSubscribed(uint8_t input, bool captured) +{ + if (captured) + subscribedInputs |= input; + else + subscribedInputs &= ~input; +} + +// Checks if a specific input is enabled for this applet and should be sent to it +bool InkHUD::Applet::isInputSubscribed(InputMask input) +{ + return (subscribedInputs & input) == input; +} + // Tell InkHUD::Renderer that we want to render now // Applets should internally listen for events they are interested in, via MeshModule, CallbackObserver etc // When an applet decides it has heard something important, and wants to redraw, it calls this method @@ -310,7 +327,7 @@ void InkHUD::Applet::printAt(int16_t x, int16_t y, const char *text, HorizontalA } // Print text, specifying the position of any edge / corner of the textbox -void InkHUD::Applet::printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha, VerticalAlignment va) +void InkHUD::Applet::printAt(int16_t x, int16_t y, const std::string &text, HorizontalAlignment ha, VerticalAlignment va) { printAt(x, y, text.c_str(), ha, va); } @@ -332,7 +349,7 @@ InkHUD::AppletFont InkHUD::Applet::getFont() // Parse any text which might have "special characters" // Re-encodes UTF-8 characters to match our 8-bit encoded fonts -std::string InkHUD::Applet::parse(std::string text) +std::string InkHUD::Applet::parse(const std::string &text) { return getFont().decodeUTF8(text); } @@ -359,10 +376,10 @@ std::string InkHUD::Applet::parseShortName(meshtastic_NodeInfoLite *node) } // Determine if all characters of a string are printable using the current font -bool InkHUD::Applet::isPrintable(std::string text) +bool InkHUD::Applet::isPrintable(const std::string &text) { // Scan for SUB (0x1A), which is the value assigned by AppletFont::applyEncoding if a unicode character is not handled - for (char &c : text) { + for (const char &c : text) { if (c == '\x1A') return false; } @@ -385,7 +402,7 @@ uint16_t InkHUD::Applet::getTextWidth(const char *text) // Gets rendered width of a string // Wrapper for getTextBounds -uint16_t InkHUD::Applet::getTextWidth(std::string text) +uint16_t InkHUD::Applet::getTextWidth(const std::string &text) { return getTextWidth(text.c_str()); } @@ -433,7 +450,7 @@ std::string InkHUD::Applet::hexifyNodeNum(NodeNum num) // Print text, with word wrapping // Avoids splitting words in half, instead moving the entire word to a new line wherever possible -void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std::string text) +void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, const std::string &text) { // Place the AdafruitGFX cursor to suit our "top" coord setCursor(left, top + getFont().heightAboveCursor()); @@ -490,15 +507,15 @@ void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std // Todo: rewrite making use of AdafruitGFX native text wrapping char cstr[] = {0, 0}; - int16_t l, t; - uint16_t w, h; + int16_t bx, by; + uint16_t bw, bh; for (uint16_t c = 0; c < word.length(); c++) { // Shove next char into a c string cstr[0] = word[c]; - getTextBounds(cstr, getCursorX(), getCursorY(), &l, &t, &w, &h); + getTextBounds(cstr, getCursorX(), getCursorY(), &bx, &by, &bw, &bh); // Manual newline, if next character will spill beyond screen edge - if ((l + w) > left + width) + if ((bx + bw) > left + width) setCursor(left, getCursorY() + getFont().lineHeight()); // Print next character @@ -517,7 +534,7 @@ void InkHUD::Applet::printWrapped(int16_t left, int16_t top, uint16_t width, std // Simulate running printWrapped, to determine how tall the block of text will be. // This is a wasteful way of handling things. Maybe some way to optimize in future? -uint32_t InkHUD::Applet::getWrappedTextHeight(int16_t left, uint16_t width, std::string text) +uint32_t InkHUD::Applet::getWrappedTextHeight(int16_t left, uint16_t width, const std::string &text) { // Cache the current crop region int16_t cL = cropLeft; @@ -647,7 +664,7 @@ uint16_t InkHUD::Applet::getActiveNodeCount() // For each node in db for (uint16_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { - meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); + const meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i); // Check if heard recently, and not our own node if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum()) @@ -700,7 +717,7 @@ std::string InkHUD::Applet::localizeDistance(uint32_t meters) } // Print text with a "faux bold" effect, by drawing it multiple times, offsetting slightly -void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY) +void InkHUD::Applet::printThick(int16_t xCenter, int16_t yCenter, const std::string &text, uint8_t thicknessX, uint8_t thicknessY) { // How many times to draw along x axis int16_t xStart; @@ -768,7 +785,7 @@ bool InkHUD::Applet::approveNotification(NicheGraphics::InkHUD::Notification &n) │ │ └───────────────────────────────┘ */ -void InkHUD::Applet::drawHeader(std::string text) +void InkHUD::Applet::drawHeader(const std::string &text) { // Y position for divider // - between header text and messages @@ -785,6 +802,16 @@ void InkHUD::Applet::drawHeader(std::string text) drawPixel(x, 0, BLACK); drawPixel(x, headerDivY, BLACK); // Dotted 50% } + + // Dither near battery + if (settings->optionalFeatures.batteryIcon) { + constexpr uint16_t ditherSizePx = 4; + Tile *batteryTile = ((Applet *)inkhud->getSystemApplet("BatteryIcon"))->getTile(); + const uint16_t batteryTileLeft = batteryTile->getLeft(); + const uint16_t batteryTileTop = batteryTile->getTop(); + const uint16_t batteryTileHeight = batteryTile->getHeight(); + hatchRegion(batteryTileLeft - ditherSizePx, batteryTileTop, ditherSizePx, batteryTileHeight, 2, WHITE); + } } // Get the height of the standard applet header diff --git a/src/graphics/niche/InkHUD/Applet.h b/src/graphics/niche/InkHUD/Applet.h index 69d35a234..84fd86465 100644 --- a/src/graphics/niche/InkHUD/Applet.h +++ b/src/graphics/niche/InkHUD/Applet.h @@ -89,6 +89,9 @@ class Applet : public GFX virtual void onForeground() {} virtual void onBackground() {} virtual void onShutdown() {} + + // Input Events + virtual void onButtonShortPress() {} virtual void onButtonLongPress() {} virtual void onExitShort() {} @@ -100,6 +103,18 @@ class Applet : public GFX virtual void onFreeText(char c) {} virtual void onFreeTextDone() {} virtual void onFreeTextCancel() {} + // List of inputs which can be subscribed to + enum InputMask { // | No Joystick | With Joystick | + BUTTON_SHORT = 1, // | Button Click | Joystick Center Click | + BUTTON_LONG = 2, // | Button Hold | Joystick Center Hold | + EXIT_SHORT = 4, // | no-op | Back Button Click | + EXIT_LONG = 8, // | no-op | Back Button Hold | + NAV_UP = 16, // | no-op | Joystick Up | + NAV_DOWN = 32, // | no-op | Joystick Down | + NAV_LEFT = 64, // | no-op | Joystick Left | + NAV_RIGHT = 128 // | no-op | Joystick Right | + }; + bool isInputSubscribed(InputMask input); // Check if input should be handled by applet, this should not be overloaded. virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification @@ -121,20 +136,28 @@ class Applet : public GFX void setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region void resetCrop(); // Removes setCrop() + // User Input Handling + + uint8_t subscribedInputs = 0b00000000; // Maybe uint16_t for futureproofing? other devices may need more inputs + void setInputsSubscribed(uint8_t input, + bool captured); // Set if an input should be handled by applet or not, this should not be + // overloaded. Can take multiple inputs at once if you OR/`|` them together + // Text void setFont(AppletFont f); AppletFont getFont(); - uint16_t getTextWidth(std::string text); + uint16_t getTextWidth(const std::string &text); uint16_t getTextWidth(const char *text); - uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text); // Result of printWrapped + uint32_t getWrappedTextHeight(int16_t left, uint16_t width, const std::string &text); // Result of printWrapped void printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP); - void printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP); - void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY); // Faux bold - void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text); // Per-word line wrapping + void printAt(int16_t x, int16_t y, const std::string &text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP); + void printThick(int16_t xCenter, int16_t yCenter, const std::string &text, uint8_t thicknessX, + uint8_t thicknessY); // Faux bold + void printWrapped(int16_t left, int16_t top, uint16_t width, const std::string &text); // Per-word line wrapping void hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines - void drawHeader(std::string text); // Draw the standard applet header + void drawHeader(const std::string &text); // Draw the standard applet header // Meshtastic Logo @@ -150,9 +173,9 @@ class Applet : public GFX std::string getTimeString(); // Current time, human readable uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric - std::string parse(std::string text); // Handle text which might contain special chars + std::string parse(const std::string &text); // Handle text which might contain special chars std::string parseShortName(meshtastic_NodeInfoLite *node); // Get the shortname, or a substitute if has unprintable chars - bool isPrintable(std::string); // Check for characters which the font can't print + bool isPrintable(const std::string &text); // Check for characters which the font can't print // Convenient references diff --git a/src/graphics/niche/InkHUD/AppletFont.cpp b/src/graphics/niche/InkHUD/AppletFont.cpp index 93a621ee8..188671a0e 100644 --- a/src/graphics/niche/InkHUD/AppletFont.cpp +++ b/src/graphics/niche/InkHUD/AppletFont.cpp @@ -39,11 +39,11 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding // Caution: signed and unsigned types int8_t glyphAscender = 0 - gfxFont->glyph[i].yOffset; if (glyphAscender > 0) - this->ascenderHeight = max(this->ascenderHeight, (uint8_t)glyphAscender); + this->ascenderHeight = max(this->ascenderHeight, static_cast(glyphAscender)); int8_t glyphDescender = gfxFont->glyph[i].height + gfxFont->glyph[i].yOffset; if (glyphDescender > 0) - this->descenderHeight = max(this->descenderHeight, (uint8_t)glyphDescender); + this->descenderHeight = max(this->descenderHeight, static_cast(glyphDescender)); } // Apply any manual padding to grow or shrink the line size @@ -52,7 +52,7 @@ InkHUD::AppletFont::AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding descenderHeight += paddingBottom; // Find how far the cursor advances when we "print" a space character - spaceCharWidth = gfxFont->glyph[(uint8_t)' ' - gfxFont->first].xAdvance; + spaceCharWidth = gfxFont->glyph[static_cast(' ') - gfxFont->first].xAdvance; } /* @@ -98,7 +98,7 @@ uint8_t InkHUD::AppletFont::widthBetweenWords() // Convert a unicode char from set of UTF-8 bytes to UTF-32 // Used by AppletFont::applyEncoding, which remaps unicode chars for extended ASCII fonts, based on their UTF-32 value -uint32_t InkHUD::AppletFont::toUtf32(std::string utf8) +uint32_t InkHUD::AppletFont::toUtf32(const std::string &utf8) { uint32_t utf32 = 0; @@ -132,7 +132,7 @@ uint32_t InkHUD::AppletFont::toUtf32(std::string utf8) // Process a string, collating UTF-8 bytes, and sending them off for re-encoding to extended ASCII // Not all InkHUD text is passed through here, only text which could potentially contain non-ASCII chars -std::string InkHUD::AppletFont::decodeUTF8(std::string encoded) +std::string InkHUD::AppletFont::decodeUTF8(const std::string &encoded) { // Final processed output std::string decoded; @@ -141,7 +141,7 @@ std::string InkHUD::AppletFont::decodeUTF8(std::string encoded) std::string utf8Char; uint8_t utf8CharSize = 0; - for (char &c : encoded) { + for (const char &c : encoded) { // If first byte if (utf8Char.empty()) { @@ -178,7 +178,7 @@ std::string InkHUD::AppletFont::decodeUTF8(std::string encoded) // Re-encode a single UTF-8 character to extended ASCII // Target encoding depends on the font -char InkHUD::AppletFont::applyEncoding(std::string utf8) +char InkHUD::AppletFont::applyEncoding(const std::string &utf8) { // ##################################################### Syntactic Sugar ##################################################### #define REMAP(in, out) \ diff --git a/src/graphics/niche/InkHUD/AppletFont.h b/src/graphics/niche/InkHUD/AppletFont.h index 02ba13c31..8374c7f61 100644 --- a/src/graphics/niche/InkHUD/AppletFont.h +++ b/src/graphics/niche/InkHUD/AppletFont.h @@ -30,20 +30,21 @@ class AppletFont }; AppletFont(); - AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding = ASCII, int8_t paddingTop = 0, int8_t paddingBottom = 0); + explicit AppletFont(const GFXfont &adafruitGFXFont, Encoding encoding = ASCII, int8_t paddingTop = 0, + int8_t paddingBottom = 0); uint8_t lineHeight(); uint8_t heightAboveCursor(); uint8_t heightBelowCursor(); uint8_t widthBetweenWords(); // Width of the space character - std::string decodeUTF8(std::string encoded); + std::string decodeUTF8(const std::string &encoded); - const GFXfont *gfxFont = NULL; // Default value: in-built AdafruitGFX font + const GFXfont *gfxFont = nullptr; // Default value: in-built AdafruitGFX font private: - uint32_t toUtf32(std::string utf8); - char applyEncoding(std::string utf8); + uint32_t toUtf32(const std::string &utf8); + char applyEncoding(const std::string &utf8); uint8_t height = 8; // Default value: in-built AdafruitGFX font uint8_t ascenderHeight = 0; // Default value: in-built AdafruitGFX font diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp index 9794c3efb..a063c08b5 100644 --- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp @@ -81,25 +81,25 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke uint8_t InkHUD::NodeListApplet::maxCards() { // Cache result. Shouldn't change during execution - static uint8_t cards = 0; + static uint8_t maxCardCount = 0; - if (!cards) { + if (!maxCardCount) { const uint16_t height = Tile::maxDisplayDimension(); // Use a loop instead of arithmetic, because it's easier for my brain to follow // Add cards one by one, until the latest card extends below screen uint16_t y = cardH; // First card: no margin above - cards = 1; + maxCardCount = 1; while (y < height) { y += cardMarginH; y += cardH; - cards++; + maxCardCount++; } } - return cards; + return maxCardCount; } // Draw, using info which derived applet placed into NodeListApplet::cards for us @@ -137,12 +137,12 @@ void InkHUD::NodeListApplet::onRender(bool full) // Gather info // ======================================== - NodeNum &nodeNum = card->nodeNum; + const NodeNum &nodeNum = card->nodeNum; SignalStrength &signal = card->signal; std::string longName; // handled below std::string shortName; // handled below std::string distance; // handled below; - uint8_t &hopsAway = card->hopsAway; + const uint8_t &hopsAway = card->hopsAway; meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum); diff --git a/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp b/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp new file mode 100644 index 000000000..79133719a --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.cpp @@ -0,0 +1,79 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD +#include "./UserAppletInputExample.h" + +using namespace NicheGraphics; + +void InkHUD::UserAppletInputExampleApplet::onActivate() +{ + setGrabbed(false); +} + +void InkHUD::UserAppletInputExampleApplet::onRender(bool full) +{ + drawHeader("Input Example"); + uint16_t headerHeight = getHeaderHeight(); + + std::string buttonName; + if (settings->joystick.enabled) + buttonName = "joystick center button"; + else + buttonName = "user button"; + + std::string additional = " | Control is grabbed, long press " + buttonName + " to release controls"; + if (!isGrabbed) + additional = " | Control is released, long press " + buttonName + " to grab controls"; + + printWrapped(0, headerHeight, width(), "Last button: " + lastInput + additional); +} + +void InkHUD::UserAppletInputExampleApplet::setGrabbed(bool grabbed) +{ + isGrabbed = grabbed; + setInputsSubscribed(BUTTON_SHORT | EXIT_SHORT | EXIT_LONG | NAV_UP | NAV_DOWN | NAV_LEFT | NAV_RIGHT, + grabbed); // Enables/disables grabbing all inputs + setInputsSubscribed(BUTTON_LONG, true); // Always grab this input +} + +void InkHUD::UserAppletInputExampleApplet::onButtonShortPress() +{ + lastInput = "BUTTON_SHORT"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onButtonLongPress() +{ + lastInput = "BUTTON_LONG"; + setGrabbed(!isGrabbed); + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onExitShort() +{ + lastInput = "EXIT_SHORT"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onExitLong() +{ + lastInput = "EXIT_LONG"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onNavUp() +{ + lastInput = "NAV_UP"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onNavDown() +{ + lastInput = "NAV_DOWN"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onNavLeft() +{ + lastInput = "NAV_LEFT"; + requestUpdate(); +} +void InkHUD::UserAppletInputExampleApplet::onNavRight() +{ + lastInput = "NAV_RIGHT"; + requestUpdate(); +} + +#endif \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h b/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h new file mode 100644 index 000000000..a99dec00c --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h @@ -0,0 +1,36 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +#pragma once + +#include "configuration.h" + +#include "graphics/niche/InkHUD/Applet.h" + +namespace NicheGraphics::InkHUD +{ + +class UserAppletInputExampleApplet : public Applet +{ + public: + void onActivate() override; + + void onRender(bool full) override; + void onButtonShortPress() override; + void onButtonLongPress() override; + void onExitShort() override; + void onExitLong() override; + void onNavUp() override; + void onNavDown() override; + void onNavLeft() override; + void onNavRight() override; + + private: + std::string lastInput = "None"; + bool isGrabbed = false; + + void setGrabbed(bool grabbed); +}; + +} // namespace NicheGraphics::InkHUD + +#endif \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp index 0cc6f50ed..c0850b742 100644 --- a/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp @@ -29,10 +29,10 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta // If we get a different type of status, something has gone weird elsewhere assert(status->getStatusType() == STATUS_TYPE_POWER); - meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)status; + const meshtastic::PowerStatus *pwrStatus = (const meshtastic::PowerStatus *)status; // Get the new state of charge %, and round to the nearest 10% - uint8_t newSocRounded = ((powerStatus->getBatteryChargePercent() + 5) / 10) * 10; + uint8_t newSocRounded = ((pwrStatus->getBatteryChargePercent() + 5) / 10) * 10; // If rounded value has changed, trigger a display update // It's okay to requestUpdate before we store the new value, as the update won't run until next loop() @@ -48,37 +48,27 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta void InkHUD::BatteryIconApplet::onRender(bool full) { - // Fill entire tile - // - size of icon controlled by size of tile - int16_t l = 0; - int16_t t = 0; - uint16_t w = width(); - int16_t h = height(); - - // Clear the region beneath the tile + // Clear the region beneath the tile, including the border // Most applets are drawing onto an empty frame buffer and don't need to do this // We do need to do this with the battery though, as it is an "overlay" - fillRect(l, t, w, h, WHITE); - - // Vertical centerline - const int16_t m = t + (h / 2); + fillRect(0, 0, width(), height(), WHITE); // ===================== // Draw battery outline // ===================== // Positive terminal "bump" - const int16_t &bumpL = l; - const uint16_t bumpH = h / 2; - const int16_t bumpT = m - (bumpH / 2); constexpr uint16_t bumpW = 2; + const int16_t &bumpL = 1; + const uint16_t bumpH = (height() - 2) / 2; + const int16_t bumpT = (1 + ((height() - 2) / 2)) - (bumpH / 2); fillRect(bumpL, bumpT, bumpW, bumpH, BLACK); // Main body of battery - const int16_t bodyL = bumpL + bumpW; - const int16_t &bodyT = t; - const int16_t &bodyH = h; - const int16_t bodyW = w - bumpW; + const int16_t bodyL = 1 + bumpW; + const int16_t &bodyT = 1; + const int16_t &bodyH = height() - 2; // Handle top/bottom padding + const int16_t bodyW = (width() - 1) - bumpW; // Handle 1px left pad drawRect(bodyL, bodyT, bodyW, bodyH, BLACK); // Erase join between bump and body @@ -89,12 +79,13 @@ void InkHUD::BatteryIconApplet::onRender(bool full) // =================== constexpr int16_t slicePad = 2; - const int16_t sliceL = bodyL + slicePad; + int16_t sliceL = bodyL + slicePad; const int16_t sliceT = bodyT + slicePad; const uint16_t sliceH = bodyH - (slicePad * 2); uint16_t sliceW = bodyW - (slicePad * 2); - sliceW = (sliceW * socRounded) / 100; // Apply percentage + sliceW = (sliceW * socRounded) / 100; // Apply percentage + sliceL += ((bodyW - (slicePad * 2)) - sliceW); // Shift slice to the battery's negative terminal, correcting drain direction hatchRegion(sliceL, sliceT, sliceW, sliceH, 2, BLACK); drawRect(sliceL, sliceT, sliceW, sliceH, BLACK); diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 6a141f73e..26d6f03d3 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -1176,7 +1176,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) items.push_back(MenuItem("Back", previousPage)); for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { - meshtastic_Channel &ch = channels.getByIndex(i); + const meshtastic_Channel &ch = channels.getByIndex(i); if (!ch.has_settings) continue; @@ -1252,7 +1252,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page) case NODE_CONFIG_CHANNEL_PRECISION: { previousPage = MenuPage::NODE_CONFIG_CHANNEL_DETAIL; items.push_back(MenuItem("Back", previousPage)); - meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); + const meshtastic_Channel &ch = channels.getByIndex(selectedChannelIndex); if (!ch.settings.has_module_settings || ch.settings.module_settings.position_precision == 0) { items.push_back(MenuItem("Position is Off", MenuPage::NODE_CONFIG_CHANNEL_DETAIL)); break; @@ -1759,7 +1759,7 @@ void InkHUD::MenuApplet::populateRecipientPage() for (uint8_t i = 0; i < MAX_NUM_CHANNELS; i++) { // Get the channel, and check if it's enabled - meshtastic_Channel &channel = channels.getByIndex(i); + const meshtastic_Channel &channel = channels.getByIndex(i); if (!channel.has_settings || channel.role == meshtastic_Channel_Role_DISABLED) continue; @@ -1829,7 +1829,7 @@ void InkHUD::MenuApplet::populateRecipientPage() items.push_back(MenuItem("Exit", MenuPage::EXIT)); } -void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, std::string text) +void InkHUD::MenuApplet::drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, const std::string &text) { setFont(fontSmall); uint16_t wrapMaxH = 0; diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h index 7b092153b..b5c1c86e4 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.h @@ -55,7 +55,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height, - std::string text); // Draw input field for free text + const std::string &text); // Draw input field for free text uint16_t getSystemInfoPanelHeight(); void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width, uint16_t *height = nullptr); // Info panel at top of root menu diff --git a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp index 19cef4fbd..6c8069c8b 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp @@ -228,17 +228,17 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila Notification::Type::NOTIFICATION_MESSAGE_BROADCAST)) { // Although we are handling DM and broadcast notifications together, we do need to treat them slightly differently - bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST; + bool msgIsBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST; // Pick source of message - MessageStore::Message *message = - isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm; + const MessageStore::Message *message = + msgIsBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm; // Find info about the sender meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender); // Leading tag (channel vs. DM) - text += isBroadcast ? "From:" : "DM: "; + text += msgIsBroadcast ? "From:" : "DM: "; // Sender id if (node && node->has_user) @@ -252,7 +252,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila text.clear(); // Leading tag (channel vs. DM) - text += isBroadcast ? "Msg from " : "DM from "; + text += msgIsBroadcast ? "Msg from " : "DM from "; // Sender id if (node && node->has_user) diff --git a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp index a09ff55d5..54515b296 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Pairing/PairingApplet.cpp @@ -55,12 +55,12 @@ int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *sta // We'll mimic that behavior, just to keep in line with the other Statuses, // even though I'm not sure what the original reason for jumping through these extra hoops was. assert(status->getStatusType() == STATUS_TYPE_BLUETOOTH); - meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)status; + const auto *btStatus = static_cast(status); // When pairing begins - if (bluetoothStatus->getConnectionState() == meshtastic::BluetoothStatus::ConnectionState::PAIRING) { + if (btStatus->getConnectionState() == meshtastic::BluetoothStatus::ConnectionState::PAIRING) { // Store the passkey for rendering - passkey = bluetoothStatus->getPasskey(); + passkey = btStatus->getPasskey(); // Show pairing screen bringToForeground(); diff --git a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp new file mode 100644 index 000000000..6963df54a --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp @@ -0,0 +1,111 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +#include "./FavoritesMapApplet.h" +#include "NodeDB.h" + +using namespace NicheGraphics; + +bool InkHUD::FavoritesMapApplet::shouldDrawNode(meshtastic_NodeInfoLite *node) +{ + // Keep our own node available as map anchor/center; all others must be favorited. + return node && (node->num == nodeDB->getNodeNum() || node->is_favorite); +} + +void InkHUD::FavoritesMapApplet::onRender(bool full) +{ + // Custom empty state text for favorites-only map. + if (!enoughMarkers()) { + printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Favorite node position", CENTER, MIDDLE); + printAt(X(0.5), Y(0.5) + (getFont().lineHeight() / 2), "will appear here", CENTER, MIDDLE); + return; + } + + // Draw the usual map applet first. + MapApplet::onRender(full); + + // Draw our latest "node of interest" as a special marker. + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(lastFrom); + if (node && node->is_favorite && nodeDB->hasValidPosition(node) && enoughMarkers()) + drawLabeledMarker(node); +} + +// Determine if we need to redraw the map, when we receive a new position packet. +ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshPacket &mp) +{ + // If applet is not active, we shouldn't be handling any data. + if (!isActive()) + return ProcessMessage::CONTINUE; + + // Try decode a position from the packet. + bool hasPosition = false; + float lat; + float lng; + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == meshtastic_PortNum_POSITION_APP) { + meshtastic_Position position = meshtastic_Position_init_default; + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Position_msg, &position)) { + if (position.has_latitude_i && position.has_longitude_i // Actually has position + && (position.latitude_i != 0 || position.longitude_i != 0)) // Position isn't "null island" + { + hasPosition = true; + lat = position.latitude_i * 1e-7; // Convert from Meshtastic's internal int32_t format + lng = position.longitude_i * 1e-7; + } + } + } + + // Skip if we didn't get a valid position. + if (!hasPosition) + return ProcessMessage::CONTINUE; + + const int8_t hopsAway = getHopsAway(mp); + const bool hasHopsAway = hopsAway >= 0; + + // Determine if the position packet would change anything on-screen. + bool somethingChanged = false; + + // If our own position. + if (isFromUs(&mp)) { + // Ignore tiny local movement to reduce update spam. + if (GeoCoord::latLongToMeter(ourLastLat, ourLastLng, lat, lng) > 50) { + somethingChanged = true; + ourLastLat = lat; + ourLastLng = lng; + } + } else { + // For non-local packets, this applet only reacts to favorited nodes. + meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); + if (!sender || !sender->is_favorite) + return ProcessMessage::CONTINUE; + + // Check if this position is from someone different than our previous position packet. + if (mp.from != lastFrom) { + somethingChanged = true; + lastFrom = mp.from; + lastLat = lat; + lastLng = lng; + lastHopsAway = hopsAway; + } + + // Same sender: check if position changed. + else if (GeoCoord::latLongToMeter(lastLat, lastLng, lat, lng) > 10) { + somethingChanged = true; + lastLat = lat; + lastLng = lng; + } + + // Same sender, same position: check if hops changed. + else if (hasHopsAway && (hopsAway != lastHopsAway)) { + somethingChanged = true; + lastHopsAway = hopsAway; + } + } + + if (somethingChanged) { + requestAutoshow(); + requestUpdate(); + } + + return ProcessMessage::CONTINUE; +} + +#endif diff --git a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h new file mode 100644 index 000000000..da5fb0dc3 --- /dev/null +++ b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h @@ -0,0 +1,44 @@ +#ifdef MESHTASTIC_INCLUDE_INKHUD + +/* + +Plots position of favorited nodes from DB, with North facing up. +Scaled to fit the most distant node. +Size of marker represents hops away. +The favorite node which most recently sent a position will be labeled. + +*/ + +#pragma once + +#include "configuration.h" + +#include "graphics/niche/InkHUD/Applets/Bases/Map/MapApplet.h" + +#include "SinglePortModule.h" + +namespace NicheGraphics::InkHUD +{ + +class FavoritesMapApplet : public MapApplet, public SinglePortModule +{ + public: + FavoritesMapApplet() : SinglePortModule("FavoritesMapApplet", meshtastic_PortNum_POSITION_APP) {} + void onRender(bool full) override; + + protected: + bool shouldDrawNode(meshtastic_NodeInfoLite *node) override; + ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + NodeNum lastFrom = 0; // Sender of most recent favorited (non-local) position packet + float lastLat = 0.0; + float lastLng = 0.0; + float lastHopsAway = 0; + + float ourLastLat = 0.0; // Info about most recent local position + float ourLastLng = 0.0; +}; + +} // namespace NicheGraphics::InkHUD + +#endif diff --git a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp index 5a659c606..a7fd094e6 100644 --- a/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp @@ -69,9 +69,10 @@ void InkHUD::HeardApplet::populateFromNodeDB() } // Sort the collection by age - std::sort(ordered.begin(), ordered.end(), [](meshtastic_NodeInfoLite *top, meshtastic_NodeInfoLite *bottom) -> bool { - return (top->last_heard > bottom->last_heard); - }); + std::sort(ordered.begin(), ordered.end(), + [](const meshtastic_NodeInfoLite *top, const meshtastic_NodeInfoLite *bottom) -> bool { + return (top->last_heard > bottom->last_heard); + }); // Keep the most recent entries only // Just enough to fill the screen diff --git a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp index f16721357..01bdc2224 100644 --- a/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp @@ -69,7 +69,7 @@ void InkHUD::ThreadedMessageApplet::onRender(bool full) while (msgB >= (0 - fontSmall.lineHeight()) && i < store->messages.size()) { // Grab data for message - MessageStore::Message &m = store->messages.at(i); + const MessageStore::Message &m = store->messages.at(i); bool outgoing = (m.sender == 0) || (m.sender == myNodeInfo.my_node_num); // Own NodeNum if canned message std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message diff --git a/src/graphics/niche/InkHUD/Events.cpp b/src/graphics/niche/InkHUD/Events.cpp index e6c16d350..577a773bb 100644 --- a/src/graphics/niche/InkHUD/Events.cpp +++ b/src/graphics/niche/InkHUD/Events.cpp @@ -59,10 +59,16 @@ void InkHUD::Events::onButtonShort() if (consumer) { consumer->onButtonShortPress(); } else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module - if (!settings->joystick.enabled) - inkhud->nextApplet(); - else - inkhud->openMenu(); + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::BUTTON_SHORT)) + userConsumer->onButtonShortPress(); + else { + if (!settings->joystick.enabled) + inkhud->nextApplet(); + else + inkhud->openMenu(); + } } } @@ -84,8 +90,14 @@ void InkHUD::Events::onButtonLong() // If no system applet is handling input, default behavior instead is to open the menu if (consumer) consumer->onButtonLongPress(); - else - inkhud->openMenu(); + else { + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::BUTTON_LONG)) + userConsumer->onButtonLongPress(); + else + inkhud->openMenu(); + } } void InkHUD::Events::onExitShort() @@ -110,8 +122,14 @@ void InkHUD::Events::onExitShort() // If no system applet is handling input, default behavior instead is change tiles if (consumer) consumer->onExitShort(); - else if (!dismissedExt) // Don't change tile if this button press silenced the external notification module - inkhud->nextTile(); + else if (!dismissedExt) { // Don't change tile if this button press silenced the external notification module + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_SHORT)) + userConsumer->onExitShort(); + else + inkhud->nextTile(); + } } } @@ -133,6 +151,13 @@ void InkHUD::Events::onExitLong() if (consumer) consumer->onExitLong(); + else { + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::EXIT_LONG)) + userConsumer->onExitLong(); + // Nothing uses exit long yet + } } } @@ -157,6 +182,12 @@ void InkHUD::Events::onNavUp() if (consumer) consumer->onNavUp(); + else if (!dismissedExt) { + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_UP)) + userConsumer->onNavUp(); + } } } @@ -181,6 +212,12 @@ void InkHUD::Events::onNavDown() if (consumer) consumer->onNavDown(); + else if (!dismissedExt) { + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_DOWN)) + userConsumer->onNavDown(); + } } } @@ -206,8 +243,14 @@ void InkHUD::Events::onNavLeft() // If no system applet is handling input, default behavior instead is to cycle applets if (consumer) consumer->onNavLeft(); - else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module - inkhud->prevApplet(); + else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_LEFT)) + userConsumer->onNavLeft(); + else + inkhud->prevApplet(); + } } } @@ -233,8 +276,14 @@ void InkHUD::Events::onNavRight() // If no system applet is handling input, default behavior instead is to cycle applets if (consumer) consumer->onNavRight(); - else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module - inkhud->nextApplet(); + else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module + Applet *userConsumer = inkhud->getActiveApplet(); + + if (userConsumer != nullptr && userConsumer->isInputSubscribed(Applet::NAV_RIGHT)) + userConsumer->onNavRight(); + else + inkhud->nextApplet(); + } } } diff --git a/src/graphics/niche/InkHUD/InkHUD.cpp b/src/graphics/niche/InkHUD/InkHUD.cpp index 5fab67639..edffda6b7 100644 --- a/src/graphics/niche/InkHUD/InkHUD.cpp +++ b/src/graphics/niche/InkHUD/InkHUD.cpp @@ -210,6 +210,12 @@ void InkHUD::InkHUD::prevApplet() windowManager->prevApplet(); } +// Returns the currently active applet +InkHUD::Applet *InkHUD::InkHUD::getActiveApplet() +{ + return windowManager->getActiveApplet(); +} + // Show the menu (on the the focused tile) // The applet previously displayed there will be restored once the menu closes void InkHUD::InkHUD::openMenu() diff --git a/src/graphics/niche/InkHUD/InkHUD.h b/src/graphics/niche/InkHUD/InkHUD.h index ae029137e..0e25b0900 100644 --- a/src/graphics/niche/InkHUD/InkHUD.h +++ b/src/graphics/niche/InkHUD/InkHUD.h @@ -74,6 +74,7 @@ class InkHUD void nextApplet(); void prevApplet(); + NicheGraphics::InkHUD::Applet *getActiveApplet(); void openMenu(); void openAlignStick(); void openKeyboard(); diff --git a/src/graphics/niche/InkHUD/MessageStore.cpp b/src/graphics/niche/InkHUD/MessageStore.cpp index 94e0aa661..44a1ef633 100644 --- a/src/graphics/niche/InkHUD/MessageStore.cpp +++ b/src/graphics/niche/InkHUD/MessageStore.cpp @@ -12,7 +12,7 @@ using namespace NicheGraphics; constexpr uint8_t MAX_MESSAGES_SAVED = 10; constexpr uint32_t MAX_MESSAGE_SIZE = 250; -InkHUD::MessageStore::MessageStore(std::string label) +InkHUD::MessageStore::MessageStore(const std::string &label) { filename = ""; filename += "/NicheGraphics"; @@ -50,12 +50,13 @@ void InkHUD::MessageStore::saveToFlash() // For each message for (uint8_t i = 0; i < messages.size() && i < MAX_MESSAGES_SAVED; i++) { Message &m = messages.at(i); - f.write((uint8_t *)&m.timestamp, sizeof(m.timestamp)); // Write timestamp. 4 bytes - f.write((uint8_t *)&m.sender, sizeof(m.sender)); // Write sender NodeId. 4 Bytes - f.write((uint8_t *)&m.channelIndex, sizeof(m.channelIndex)); // Write channel index. 1 Byte - f.write((uint8_t *)m.text.c_str(), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text. Variable length - f.write('\0'); // Append null term - LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", (uint32_t)i, min(MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str()); + f.write(reinterpret_cast(&m.timestamp), sizeof(m.timestamp)); // Write timestamp. 4 bytes + f.write(reinterpret_cast(&m.sender), sizeof(m.sender)); // Write sender NodeId. 4 Bytes + f.write(reinterpret_cast(&m.channelIndex), sizeof(m.channelIndex)); // Write channel index. 1 Byte + f.write(reinterpret_cast(m.text.c_str()), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text + f.write('\0'); // Append null term + LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast(i), min(MAX_MESSAGE_SIZE, m.text.size()), + m.text.c_str()); } // Release firmware's SPI lock, because SafeFile::close needs it @@ -111,17 +112,17 @@ void InkHUD::MessageStore::loadFromFlash() // First byte: how many messages are in the flash store uint8_t flashMessageCount = 0; - f.readBytes((char *)&flashMessageCount, 1); - LOG_DEBUG("Messages available: %u", (uint32_t)flashMessageCount); + f.readBytes(reinterpret_cast(&flashMessageCount), 1); + LOG_DEBUG("Messages available: %u", static_cast(flashMessageCount)); // For each message for (uint8_t i = 0; i < flashMessageCount && i < MAX_MESSAGES_SAVED; i++) { Message m; // Read meta data (fixed width) - f.readBytes((char *)&m.timestamp, sizeof(m.timestamp)); - f.readBytes((char *)&m.sender, sizeof(m.sender)); - f.readBytes((char *)&m.channelIndex, sizeof(m.channelIndex)); + f.readBytes(reinterpret_cast(&m.timestamp), sizeof(m.timestamp)); + f.readBytes(reinterpret_cast(&m.sender), sizeof(m.sender)); + f.readBytes(reinterpret_cast(&m.channelIndex), sizeof(m.channelIndex)); // Read characters until we find a null term char c; @@ -136,7 +137,8 @@ void InkHUD::MessageStore::loadFromFlash() // Store in RAM messages.push_back(m); - LOG_DEBUG("#%u, timestamp=%u, sender(num)=%u, text=\"%s\"", (uint32_t)i, m.timestamp, m.sender, m.text.c_str()); + LOG_DEBUG("#%u, timestamp=%u, sender(num)=%u, text=\"%s\"", static_cast(i), m.timestamp, m.sender, + m.text.c_str()); } f.close(); diff --git a/src/graphics/niche/InkHUD/MessageStore.h b/src/graphics/niche/InkHUD/MessageStore.h index 745c3b2eb..55fb9b8cc 100644 --- a/src/graphics/niche/InkHUD/MessageStore.h +++ b/src/graphics/niche/InkHUD/MessageStore.h @@ -31,7 +31,7 @@ class MessageStore }; MessageStore() = delete; - explicit MessageStore(std::string label); // Label determines filename in flash + explicit MessageStore(const std::string &label); // Label determines filename in flash void saveToFlash(); void loadFromFlash(); diff --git a/src/graphics/niche/InkHUD/PlatformioConfig.ini b/src/graphics/niche/InkHUD/PlatformioConfig.ini index b985f9f77..67ad5098f 100644 --- a/src/graphics/niche/InkHUD/PlatformioConfig.ini +++ b/src/graphics/niche/InkHUD/PlatformioConfig.ini @@ -8,5 +8,5 @@ build_flags = -D MESHTASTIC_EXCLUDE_INPUTBROKER ; Suppress default input handling -D HAS_BUTTON=0 ; Suppress default ButtonThread lib_deps = - # TODO renovate - https://github.com/ZinggJM/GFX_Root#2.0.0 ; Used by InkHUD as a "slimmer" version of AdafruitGFX + # renovate: datasource=github-tags depName=GFX_Root packageName=ZinggJM/GFX_Root + https://github.com/ZinggJM/GFX_Root/archive/3195764e352a0d2567c8d277ac408ca7293a99b0.zip ; Used by InkHUD as a "slimmer" version of AdafruitGFX diff --git a/src/graphics/niche/InkHUD/Renderer.cpp b/src/graphics/niche/InkHUD/Renderer.cpp index 89a83c932..a73e209ff 100644 --- a/src/graphics/niche/InkHUD/Renderer.cpp +++ b/src/graphics/niche/InkHUD/Renderer.cpp @@ -269,42 +269,42 @@ void InkHUD::Renderer::clearTile(Tile *t) // Rotate the tile dimensions int16_t left = 0; int16_t top = 0; - uint16_t width = 0; - uint16_t height = 0; + uint16_t tileW = 0; + uint16_t tileH = 0; switch (settings->rotation) { case 0: left = t->getLeft(); top = t->getTop(); - width = t->getWidth(); - height = t->getHeight(); + tileW = t->getWidth(); + tileH = t->getHeight(); break; case 1: left = driver->width - (t->getTop() + t->getHeight()); top = t->getLeft(); - width = t->getHeight(); - height = t->getWidth(); + tileW = t->getHeight(); + tileH = t->getWidth(); break; case 2: left = driver->width - (t->getLeft() + t->getWidth()); top = driver->height - (t->getTop() + t->getHeight()); - width = t->getWidth(); - height = t->getHeight(); + tileW = t->getWidth(); + tileH = t->getHeight(); break; case 3: left = t->getTop(); top = driver->height - (t->getLeft() + t->getWidth()); - width = t->getHeight(); - height = t->getWidth(); + tileW = t->getHeight(); + tileH = t->getWidth(); break; } // Calculate the bounds to clear uint16_t xStart = (left < 0) ? 0 : left; uint16_t yStart = (top < 0) ? 0 : top; - if (xStart >= driver->width || yStart >= driver->height || left + width < 0 || top + height < 0) + if (xStart >= driver->width || yStart >= driver->height || left + tileW < 0 || top + tileH < 0) return; // the box is completely off the screen - uint16_t xEnd = left + width; - uint16_t yEnd = top + height; + uint16_t xEnd = left + tileW; + uint16_t yEnd = top + tileH; if (xEnd > driver->width) xEnd = driver->width; if (yEnd > driver->height) diff --git a/src/graphics/niche/InkHUD/WindowManager.cpp b/src/graphics/niche/InkHUD/WindowManager.cpp index 9c18fbd48..fce3c9770 100644 --- a/src/graphics/niche/InkHUD/WindowManager.cpp +++ b/src/graphics/niche/InkHUD/WindowManager.cpp @@ -273,6 +273,12 @@ void InkHUD::WindowManager::prevApplet() inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST } +// Returns active applet +NicheGraphics::InkHUD::Applet *InkHUD::WindowManager::getActiveApplet() +{ + return userTiles.at(settings->userTiles.focused)->getAssignedApplet(); +} + // Rotate the display image by 90 degrees void InkHUD::WindowManager::rotate() { @@ -396,7 +402,7 @@ void InkHUD::WindowManager::autoshow() { // Don't perform autoshow if a system applet has exclusive use of the display right now // Note: lockRequests prevents autoshow attempting to hide menuApplet - for (SystemApplet *sa : inkhud->systemApplets) { + for (const SystemApplet *sa : inkhud->systemApplets) { if (sa->lockRendering || sa->lockRequests) return; } @@ -510,10 +516,10 @@ void InkHUD::WindowManager::placeSystemTiles() const uint16_t batteryIconWidth = batteryIconHeight * 1.8; inkhud->getSystemApplet("BatteryIcon") ->getTile() - ->setRegion(inkhud->width() - batteryIconWidth, // x - 2, // y - batteryIconWidth, // width - batteryIconHeight); // height + ->setRegion(inkhud->width() - batteryIconWidth - 1, // x + 1, // y + batteryIconWidth + 1, // width + batteryIconHeight + 2); // height // Note: the tiles of placeholder and menu applets are manipulated specially // - menuApplet borrows user tiles diff --git a/src/graphics/niche/InkHUD/WindowManager.h b/src/graphics/niche/InkHUD/WindowManager.h index 948ef6131..a11688cf5 100644 --- a/src/graphics/niche/InkHUD/WindowManager.h +++ b/src/graphics/niche/InkHUD/WindowManager.h @@ -29,6 +29,7 @@ class WindowManager void nextTile(); void prevTile(); + Applet *getActiveApplet(); void openMenu(); void openAlignStick(); void openKeyboard(); diff --git a/src/graphics/niche/Utils/CannedMessageStore.cpp b/src/graphics/niche/Utils/CannedMessageStore.cpp index 50998930d..182b7e1f8 100644 --- a/src/graphics/niche/Utils/CannedMessageStore.cpp +++ b/src/graphics/niche/Utils/CannedMessageStore.cpp @@ -146,7 +146,7 @@ void CannedMessageStore::handleGet(meshtastic_AdminMessage *response) std::string merged; if (!messages.empty()) { // Don't run if no messages: error on pop_back with size=0 merged.reserve(201); - for (std::string &s : messages) { + for (const std::string &s : messages) { merged += s; merged += '|'; } diff --git a/src/input/HackadayCommunicatorKeyboard.cpp b/src/input/HackadayCommunicatorKeyboard.cpp index c6a9e0ae8..b096c74d2 100644 --- a/src/input/HackadayCommunicatorKeyboard.cpp +++ b/src/input/HackadayCommunicatorKeyboard.cpp @@ -106,8 +106,8 @@ static unsigned char HackadayCommunicatorTapMap[_TCA8418_NUM_KEYS][2] = {{}, {}}; HackadayCommunicatorKeyboard::HackadayCommunicatorKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), - last_tap(0L), char_idx(0), tap_interval(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX), + next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0) { reset(); } @@ -147,7 +147,6 @@ void HackadayCommunicatorKeyboard::pressed(uint8_t key) modifierFlag = 0; } - uint8_t next_key = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { @@ -187,8 +186,8 @@ void HackadayCommunicatorKeyboard::released() return; } - if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { - last_key = -1; + if (last_key >= _TCA8418_NUM_KEYS) { + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/HackadayCommunicatorKeyboard.h b/src/input/HackadayCommunicatorKeyboard.h index 8316bed72..cbba5c12f 100644 --- a/src/input/HackadayCommunicatorKeyboard.h +++ b/src/input/HackadayCommunicatorKeyboard.h @@ -18,8 +18,8 @@ class HackadayCommunicatorKeyboard : public TCA8418KeyboardBase private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed uint32_t last_modifier_time; // Timestamp of the last modifier key press - int8_t last_key; - int8_t next_key; + uint8_t last_key; + uint8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; diff --git a/src/input/MPR121Keyboard.cpp b/src/input/MPR121Keyboard.cpp index 9bca6801d..ec37cfbaa 100644 --- a/src/input/MPR121Keyboard.cpp +++ b/src/input/MPR121Keyboard.cpp @@ -91,7 +91,7 @@ MPR121Keyboard::MPR121Keyboard() : m_wire(nullptr), m_addr(0), readCallback(null { // LOG_DEBUG("MPR121 @ %02x", m_addr); state = Init; - last_key = -1; + last_key = UINT8_MAX; last_tap = 0L; char_idx = 0; queue = ""; @@ -359,8 +359,8 @@ void MPR121Keyboard::released() return; } // would clear longpress callback... later. - if (last_key < 0 || last_key > _NUM_KEYS) { // reset to idle if last_key out of bounds - last_key = -1; + if (last_key >= _NUM_KEYS) { // reset to idle if last_key out of bounds + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/MPR121Keyboard.h b/src/input/MPR121Keyboard.h index 6349750ce..ec3f56e87 100644 --- a/src/input/MPR121Keyboard.h +++ b/src/input/MPR121Keyboard.h @@ -14,7 +14,7 @@ class MPR121Keyboard MPR121States state; - int8_t last_key; + uint8_t last_key; uint32_t last_tap; uint8_t char_idx; diff --git a/src/input/TCA8418Keyboard.cpp b/src/input/TCA8418Keyboard.cpp index bd8338acf..822885a9f 100644 --- a/src/input/TCA8418Keyboard.cpp +++ b/src/input/TCA8418Keyboard.cpp @@ -43,8 +43,8 @@ static unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = { }; TCA8418Keyboard::TCA8418Keyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(-1), next_key(-1), last_tap(0L), char_idx(0), tap_interval(0), - should_backspace(false) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), last_key(UINT8_MAX), next_key(UINT8_MAX), last_tap(0L), char_idx(0), + tap_interval(0), should_backspace(false) { } @@ -63,7 +63,6 @@ void TCA8418Keyboard::pressed(uint8_t key) if (state == Init || state == Busy) { return; } - uint8_t next_key = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; @@ -72,7 +71,7 @@ void TCA8418Keyboard::pressed(uint8_t key) } // Compute key index based on dynamic row/column - next_key = row * _TCA8418_COLS + col; + next_key = (uint8_t)(row * _TCA8418_COLS + col); // LOG_DEBUG("TCA8418: Key %u -> Next Key %u", key, next_key); @@ -106,8 +105,8 @@ void TCA8418Keyboard::released() return; } - if (last_key < 0 || last_key > _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds - last_key = -1; + if (last_key >= _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/TCA8418Keyboard.h b/src/input/TCA8418Keyboard.h index b76916643..0e8821260 100644 --- a/src/input/TCA8418Keyboard.h +++ b/src/input/TCA8418Keyboard.h @@ -14,8 +14,8 @@ class TCA8418Keyboard : public TCA8418KeyboardBase void pressed(uint8_t key) override; void released(void) override; - int8_t last_key; - int8_t next_key; + uint8_t last_key; + uint8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; diff --git a/src/input/TDeckProKeyboard.cpp b/src/input/TDeckProKeyboard.cpp index eeafe4949..b83f0c6ae 100644 --- a/src/input/TDeckProKeyboard.cpp +++ b/src/input/TDeckProKeyboard.cpp @@ -62,8 +62,8 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = { }; TDeckProKeyboard::TDeckProKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), - last_tap(0L), char_idx(0), tap_interval(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX), + next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0) { } @@ -101,7 +101,6 @@ void TDeckProKeyboard::pressed(uint8_t key) modifierFlag = 0; } - uint8_t next_key = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; @@ -142,8 +141,8 @@ void TDeckProKeyboard::released() return; } - if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { - last_key = -1; + if (last_key >= _TCA8418_NUM_KEYS) { + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/TDeckProKeyboard.h b/src/input/TDeckProKeyboard.h index 617f3f20b..3ef97fc3d 100644 --- a/src/input/TDeckProKeyboard.h +++ b/src/input/TDeckProKeyboard.h @@ -19,8 +19,8 @@ class TDeckProKeyboard : public TCA8418KeyboardBase private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed uint32_t last_modifier_time; // Timestamp of the last modifier key press - int8_t last_key; - int8_t next_key; + uint8_t last_key; + uint8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp index 9a4fd8679..4efa5d6e2 100644 --- a/src/input/TLoraPagerKeyboard.cpp +++ b/src/input/TLoraPagerKeyboard.cpp @@ -65,8 +65,8 @@ static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'}, {' ', 0x00, Key::BL_TOGGLE}}; TLoraPagerKeyboard::TLoraPagerKeyboard() - : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), - last_tap(0L), char_idx(0), tap_interval(0) + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX), + next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); @@ -129,7 +129,6 @@ void TLoraPagerKeyboard::pressed(uint8_t key) modifierFlag = 0; } - uint8_t next_key = 0; int row = (key - 1) / 10; int col = (key - 1) % 10; @@ -170,8 +169,8 @@ void TLoraPagerKeyboard::released() return; } - if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { - last_key = -1; + if (last_key >= _TCA8418_NUM_KEYS) { + last_key = UINT8_MAX; state = Idle; return; } diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h index f04d2ce6a..06a4c7b63 100644 --- a/src/input/TLoraPagerKeyboard.h +++ b/src/input/TLoraPagerKeyboard.h @@ -21,8 +21,8 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase private: uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed uint32_t last_modifier_time; // Timestamp of the last modifier key press - int8_t last_key; - int8_t next_key; + uint8_t last_key; + uint8_t next_key; uint32_t last_tap; uint8_t char_idx; int32_t tap_interval; diff --git a/src/main.cpp b/src/main.cpp index 52359a6a6..f70b3975e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,13 +7,13 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "PowerMon.h" +#include "RadioLibInterface.h" #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" #include "power/PowerHAL.h" #include "FSCommon.h" -#include "Led.h" #include "RTC.h" #include "SPILock.h" #include "Throttle.h" @@ -29,7 +29,6 @@ #include #endif #include "detect/einkScan.h" -#include "graphics/RAKled.h" #include "graphics/Screen.h" #include "main.h" #include "mesh/generated/meshtastic/config.pb.h" @@ -193,6 +192,8 @@ bool kb_found = false; // global bool to record that on-screen keyboard (OSK) is present bool osk_found = false; +unsigned long last_listen = 0; + // The I2C address of the RTC Module (if found) ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE; // The I2C address of the Accelerometer (if found) @@ -242,33 +243,13 @@ const char *getDeviceName() return name; } -// TODO remove from main.cpp -static int32_t ledBlinker() -{ - // Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if - // config.device.led_heartbeat_disabled is changed - if (config.device.led_heartbeat_disabled) - return 1000; - - static bool ledOn; - ledOn ^= 1; - - ledBlink.set(ledOn); - - // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that - return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000); -} - uint32_t timeLastPowered = 0; -static Periodic *ledPeriodic; static OSThread *powerFSMthread; -static OSThread *ambientLightingThread; +OSThread *ambientLightingThread; RadioInterface *rIf = NULL; -#ifdef ARCH_PORTDUINO RadioLibHal *RadioLibHAL = NULL; -#endif /** * Some platforms (nrf52) might provide an alterate version that suppresses calling delay from sleep. @@ -299,21 +280,16 @@ void earlyInitVariant() {} // blink user led in 3 flashes sequence to indicate what is happening void waitUntilPowerLevelSafe() { - -#ifdef LED_PIN - pinMode(LED_PIN, OUTPUT); -#endif - while (powerHAL_isPowerLevelSafe() == false) { -#ifdef LED_PIN +#ifdef LED_POWER // 3x: blink for 300 ms, pause for 300 ms for (int i = 0; i < 3; i++) { - digitalWrite(LED_PIN, LED_STATE_ON); + digitalWrite(LED_POWER, LED_STATE_ON); delay(300); - digitalWrite(LED_PIN, LED_STATE_OFF); + digitalWrite(LED_POWER, LED_STATE_OFF); delay(300); } #endif @@ -337,6 +313,11 @@ void setup() // initialize power HAL layer as early as possible powerHAL_init(); +#ifdef LED_POWER + pinMode(LED_POWER, OUTPUT); + digitalWrite(LED_POWER, LED_STATE_ON); +#endif + // prevent booting if device is in power failure mode // boot sequence will follow when battery level raises to safe mode waitUntilPowerLevelSafe(); @@ -349,11 +330,6 @@ void setup() digitalWrite(PIN_POWER_EN, HIGH); #endif -#ifdef LED_POWER - pinMode(LED_POWER, OUTPUT); - digitalWrite(LED_POWER, LED_STATE_ON); -#endif - #ifdef LED_NOTIFICATION pinMode(LED_NOTIFICATION, OUTPUT); digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON); @@ -366,11 +342,7 @@ void setup() #ifdef BLE_LED pinMode(BLE_LED, OUTPUT); -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif concurrency::hasBeenSetup = true; @@ -489,14 +461,6 @@ void setup() OSThread::setup(); - // TODO make this ifdef based on defined pins and move from main.cpp -#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2) - // The ThinkNodes have their own blink logic - // ledPeriodic = new Periodic("Blink", elecrowLedBlinker); -#else - ledPeriodic = new Periodic("Blink", ledBlinker); -#endif - fsInit(); #if !MESHTASTIC_EXCLUDE_I2C @@ -715,20 +679,12 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); #endif #ifdef HAS_SDCARD setupSDCard(); #endif - // LED init - -#ifdef LED_PIN - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LED_STATE_ON); // turn on for now -#endif - // Hello printInfo(); #ifdef BUILD_EPOCH @@ -767,17 +723,15 @@ void setup() playStartMelody(); #if HAS_SCREEN - // fixed screen override? - if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) - screen_model = config.display.oled; - + // fixed screen override? #if defined(USE_SH1107) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128 screen_geometry = GEOMETRY_128_128; -#endif - -#if defined(USE_SH1107_128_64) +#elif defined(USE_SH1107_128_64) screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64 +#else + if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) + screen_model = config.display.oled; #endif #endif @@ -838,7 +792,7 @@ void setup() SPI.begin(); #endif #else -// ESP32 + // ESP32 #if defined(HW_SPI1_DEVICE) SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); @@ -971,12 +925,6 @@ void setup() setupNicheGraphics(); #endif -#ifdef LED_PIN - // Turn LED off after boot, if heartbeat by config - if (config.device.led_heartbeat_disabled) - digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON); -#endif - // Do this after service.init (because that clears error_code) #ifdef HAS_PMU if (!pmu_found) @@ -1002,7 +950,7 @@ void setup() #endif #endif - initLoRa(); + auto rIf = initLoRa(); lateInitVariant(); // Do board specific init (see extra_variants/README.md for documentation) @@ -1051,12 +999,12 @@ void setup() if (!rIf) RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_NO_RADIO); else { - router->addInterface(rIf); - // Log bit rate to debug output LOG_DEBUG("LoRA bitrate = %f bytes / sec", (float(meshtastic_Constants_DATA_PAYLOAD_LEN) / (float(rIf->getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN)))) * 1000); + + router->addInterface(std::move(rIf)); } // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values @@ -1173,6 +1121,12 @@ void loop() #endif power->powerCommandsCheck(); + if (RadioLibInterface::instance != nullptr && !Throttle::isWithinTimespanMs(last_listen, 1000 * 60) && + !(RadioLibInterface::instance->isSending() || RadioLibInterface::instance->isActivelyReceiving())) { + RadioLibInterface::instance->startReceive(); + LOG_DEBUG("attempting AGC reset"); + } + #ifdef DEBUG_STACK static uint32_t lastPrint = 0; if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) { @@ -1192,10 +1146,7 @@ void loop() } if (portduino_status.LoRa_in_error && rebootAtMsec == 0) { LOG_ERROR("LoRa in error detected, attempting to recover"); - if (rIf != nullptr) { - delete rIf; - rIf = nullptr; - } + router->addInterface(nullptr); if (portduino_config.lora_spi_dev == "ch341") { if (ch341Hal != nullptr) { delete ch341Hal; @@ -1211,8 +1162,9 @@ void loop() exit(EXIT_FAILURE); } } - if (initLoRa()) { - router->addInterface(rIf); + auto rIf = initLoRa(); + if (rIf) { + router->addInterface(std::move(rIf)); portduino_status.LoRa_in_error = false; } else { LOG_WARN("Reconfigure failed, rebooting"); diff --git a/src/main.h b/src/main.h index 91e27951f..619eb184e 100644 --- a/src/main.h +++ b/src/main.h @@ -33,6 +33,7 @@ extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; extern bool kb_found; extern bool osk_found; +extern unsigned long last_listen; extern ScanI2C::DeviceAddress rtc_found; extern ScanI2C::DeviceAddress accelerometer_found; extern ScanI2C::FoundDevice rgb_found; diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 0f4d64113..72216a63c 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -1,6 +1,7 @@ #include "CryptoEngine.h" // #include "NodeDB.h" #include "architecture.h" +#include #if !(MESHTASTIC_EXCLUDE_PKI) #include "NodeDB.h" @@ -169,10 +170,9 @@ void CryptoEngine::hash(uint8_t *bytes, size_t numBytes) void CryptoEngine::aesSetKey(const uint8_t *key_bytes, size_t key_len) { - delete aes; aes = nullptr; if (key_len != 0) { - aes = new AESSmall256(); + aes = std::unique_ptr(new AESSmall256()); aes->setKey(key_bytes, key_len); } } @@ -231,12 +231,11 @@ void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes // Generic implementation of AES-CTR encryption. void CryptoEngine::encryptAESCtr(CryptoKey _key, uint8_t *_nonce, size_t numBytes, uint8_t *bytes) { - delete ctr; - ctr = nullptr; + std::unique_ptr ctr; if (_key.length == 16) - ctr = new CTR(); + ctr = std::unique_ptr(new CTR()); else - ctr = new CTR(); + ctr = std::unique_ptr(new CTR()); ctr->setKey(_key.bytes, _key.length); static uint8_t scratch[MAX_BLOCKSIZE]; memcpy(scratch, bytes, numBytes); diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 7689006ab..19d572355 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -5,6 +5,7 @@ #include "configuration.h" #include "mesh-pb-constants.h" #include +#include extern concurrency::Lock *cryptLock; @@ -48,7 +49,7 @@ class CryptoEngine virtual void aesSetKey(const uint8_t *key, size_t key_len); virtual void aesEncrypt(uint8_t *in, uint8_t *out); - AESSmall256 *aes = NULL; + std::unique_ptr aes = nullptr; #endif @@ -77,7 +78,6 @@ class CryptoEngine /** Our per packet nonce */ uint8_t nonce[16] = {0}; CryptoKey key = {}; - CTRCommon *ctr = NULL; #if !(MESHTASTIC_EXCLUDE_PKI) uint8_t shared_key[32] = {0}; uint8_t private_key[32] = {0}; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index f76877e65..a52fa4478 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -814,26 +814,28 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_store_forward = true; moduleConfig.has_telemetry = true; moduleConfig.has_external_notification = true; -#if defined(PIN_BUZZER) +#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) moduleConfig.external_notification.enabled = true; +#endif +#if defined(PIN_BUZZER) moduleConfig.external_notification.output_buzzer = PIN_BUZZER; moduleConfig.external_notification.use_pwm = true; moduleConfig.external_notification.alert_message_buzzer = true; - moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif #if defined(PIN_VIBRATION) - moduleConfig.external_notification.enabled = true; moduleConfig.external_notification.output_vibra = PIN_VIBRATION; moduleConfig.external_notification.alert_message_vibra = true; moduleConfig.external_notification.output_ms = 500; - moduleConfig.external_notification.nag_timeout = 2; #endif #if defined(LED_NOTIFICATION) - moduleConfig.external_notification.enabled = true; moduleConfig.external_notification.output = LED_NOTIFICATION; moduleConfig.external_notification.active = LED_STATE_ON; moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 1000; +#endif +#if defined(PIN_VIBRATION) + moduleConfig.external_notification.nag_timeout = 2; +#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs; #endif @@ -1408,6 +1410,15 @@ void NodeDB::loadFromDisk() if (portduino_config.has_configDisplayMode) { config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode; } + if (portduino_config.has_statusMessage) { + moduleConfig.has_statusmessage = true; + strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(), + sizeof(moduleConfig.statusmessage.node_status)); + moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0'; + } + if (portduino_config.enable_UDP) { + config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST; + } #endif } @@ -1548,6 +1559,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat) moduleConfig.has_ambient_lighting = true; moduleConfig.has_audio = true; moduleConfig.has_paxcounter = true; + moduleConfig.has_statusmessage = true; success &= saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); @@ -1762,7 +1774,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact) info->has_device_metrics = false; info->has_position = false; info->user.public_key.size = 0; - info->user.public_key.bytes[0] = 0; + memset(info->user.public_key.bytes, 0, sizeof(info->user.public_key.bytes)); } else { /* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with * public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM! diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 34393d259..845a936d4 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -439,7 +439,7 @@ void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, cons } // Getters and setters for hop limit fields packed in hop_limit -inline uint8_t PacketHistory::getHighestHopLimit(PacketRecord &r) +inline uint8_t PacketHistory::getHighestHopLimit(const PacketRecord &r) { return r.hop_limit & HOP_LIMIT_HIGHEST_MASK; } @@ -449,7 +449,7 @@ inline void PacketHistory::setHighestHopLimit(PacketRecord &r, uint8_t hopLimit) r.hop_limit = (r.hop_limit & ~HOP_LIMIT_HIGHEST_MASK) | (hopLimit & HOP_LIMIT_HIGHEST_MASK); } -inline uint8_t PacketHistory::getOurTxHopLimit(PacketRecord &r) +inline uint8_t PacketHistory::getOurTxHopLimit(const PacketRecord &r) { return (r.hop_limit & HOP_LIMIT_OUR_TX_MASK) >> HOP_LIMIT_OUR_TX_SHIFT; } diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 5fbad2dc9..9b6a93280 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -43,9 +43,9 @@ class PacketHistory * @return true if node was indeed a relayer, false if not */ bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr); - uint8_t getHighestHopLimit(PacketRecord &r); + uint8_t getHighestHopLimit(const PacketRecord &r); void setHighestHopLimit(PacketRecord &r, uint8_t hopLimit); - uint8_t getOurTxHopLimit(PacketRecord &r); + uint8_t getOurTxHopLimit(const PacketRecord &r); void setOurTxHopLimit(PacketRecord &r, uint8_t hopLimit); PacketHistory(const PacketHistory &); // non construction-copyable diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index 725477eae..27e653efe 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -82,7 +82,6 @@ template class ProtobufModule : protected SinglePortModule // it would be better to update even if the message was destined to others. auto &p = mp.decoded; - LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum, p.payload.size); T scratch; T *decoded = NULL; @@ -90,6 +89,8 @@ template class ProtobufModule : protected SinglePortModule memset(&scratch, 0, sizeof(scratch)); if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) { decoded = &scratch; + LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum, + p.payload.size); } else { LOG_ERROR("Error decoding proto module!"); // if we can't decode it, nobody can process it! diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 9e1ea3f21..e8202d9b0 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -227,23 +227,19 @@ static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1]; // Global LoRa radio type LoRaRadioType radioType = NO_RADIO; -extern RadioInterface *rIf; extern RadioLibHal *RadioLibHAL; #if defined(HW_SPI1_DEVICE) && defined(ARCH_ESP32) extern SPIClass SPI1; #endif -bool initLoRa() +std::unique_ptr initLoRa() { - if (rIf != nullptr) { - delete rIf; - rIf = nullptr; - } + std::unique_ptr rIf = nullptr; #if ARCH_PORTDUINO - SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); + SPISettings loraSpiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); #else - SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); + SPISettings loraSpiSettings(4000000, MSBFIRST, SPI_MODE0); #endif #ifdef ARCH_PORTDUINO @@ -252,26 +248,26 @@ bool initLoRa() RADIOLIB_PIN_TYPE busy) { switch (portduino_config.lora_module) { case use_rf95: - return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new RF95Interface(hal, cs, irq, rst, busy)); case use_sx1262: - return (RadioInterface *)new SX1262Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new SX1262Interface(hal, cs, irq, rst, busy)); case use_sx1268: - return (RadioInterface *)new SX1268Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new SX1268Interface(hal, cs, irq, rst, busy)); case use_sx1280: - return (RadioInterface *)new SX1280Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new SX1280Interface(hal, cs, irq, rst, busy)); case use_lr1110: - return (RadioInterface *)new LR1110Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new LR1110Interface(hal, cs, irq, rst, busy)); case use_lr1120: - return (RadioInterface *)new LR1120Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new LR1120Interface(hal, cs, irq, rst, busy)); case use_lr1121: - return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new LR1121Interface(hal, cs, irq, rst, busy)); case use_llcc68: - return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy); + return std::unique_ptr(new LLCC68Interface(hal, cs, irq, rst, busy)); case use_simradio: - return (RadioInterface *)new SimRadio; + return std::unique_ptr(new SimRadio); default: assert(0); // shouldn't happen - return (RadioInterface *)nullptr; + return std::unique_ptr(nullptr); } }; @@ -284,7 +280,7 @@ bool initLoRa() delete RadioLibHAL; RadioLibHAL = nullptr; } - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + RadioLibHAL = new LockingArduinoHal(SPI, loraSpiSettings); } rIf = loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin, @@ -292,27 +288,28 @@ bool initLoRa() if (!rIf->init()) { LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str()); - delete rIf; - rIf = NULL; + rIf = nullptr; exit(EXIT_FAILURE); } else { LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str()); } #elif defined(HW_SPI1_DEVICE) - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings); + LockingArduinoHal *loraHal = new LockingArduinoHal(SPI1, loraSpiSettings); + RadioLibHAL = loraHal; #else // HW_SPI1_DEVICE - LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); + LockingArduinoHal *loraHal = new LockingArduinoHal(SPI, loraSpiSettings); + RadioLibHAL = loraHal; #endif // radio init MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) #if defined(USE_STM32WLx) if (!rIf) { - rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + rIf = std::unique_ptr( + new STM32WLE5JCInterface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No STM32WL radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("STM32WL init success"); radioType = STM32WLx_RADIO; @@ -322,11 +319,10 @@ bool initLoRa() #if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1); + rIf = std::unique_ptr(new RF95Interface(loraHal, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1)); if (!rIf->init()) { LOG_WARN("No RF95 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("RF95 init success"); radioType = RF95_RADIO; @@ -336,17 +332,17 @@ bool initLoRa() #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + auto sxIf = + std::unique_ptr(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); #ifdef SX126X_DIO3_TCXO_VOLTAGE sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); #endif if (!sxIf->init()) { LOG_WARN("No SX1262 radio"); - delete sxIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1262 init success"); - rIf = sxIf; + rIf = std::move(sxIf); radioType = SX1262_RADIO; } } @@ -355,26 +351,25 @@ bool initLoRa() #if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL) if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // try using the specified TCXO voltage - auto *sxIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + auto sxIf = + std::unique_ptr(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); if (!sxIf->init()) { LOG_WARN("No SX1262 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1262 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; + rIf = std::move(sxIf); radioType = SX1262_RADIO; } } if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // If specified TCXO voltage fails, attempt to use DIO3 as a reference instead - rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + rIf = std::unique_ptr(new SX1262Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1262 radio with XTAL, Vref 0.0V"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1262 init success, XTAL, Vref 0.0V"); radioType = SX1262_RADIO; @@ -386,25 +381,24 @@ bool initLoRa() #if defined(SX126X_DIO3_TCXO_VOLTAGE) && defined(TCXO_OPTIONAL) if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // try using the specified TCXO voltage - auto *sxIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + auto sxIf = + std::unique_ptr(new SX1268Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); sxIf->setTCXOVoltage(SX126X_DIO3_TCXO_VOLTAGE); if (!sxIf->init()) { LOG_WARN("No SX1268 radio with TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - delete sxIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1268 init success, TCXO, Vref %fV", SX126X_DIO3_TCXO_VOLTAGE); - rIf = sxIf; + rIf = std::move(sxIf); radioType = SX1268_RADIO; } } #endif if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + rIf = std::unique_ptr(new SX1268Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1268 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1268 init success"); radioType = SX1268_RADIO; @@ -414,11 +408,10 @@ bool initLoRa() #if defined(USE_LLCC68) if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY); + rIf = std::unique_ptr(new LLCC68Interface(loraHal, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY)); if (!rIf->init()) { LOG_WARN("No LLCC68 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("LLCC68 init success"); radioType = LLCC68_RADIO; @@ -428,11 +421,11 @@ bool initLoRa() #if defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1 if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { - rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN); + rIf = std::unique_ptr( + new LR1110Interface(loraHal, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1110 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("LR1110 init success"); radioType = LR1110_RADIO; @@ -442,11 +435,11 @@ bool initLoRa() #if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1 if (!rIf) { - rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN); + rIf = std::unique_ptr( + new LR1120Interface(loraHal, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1120 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("LR1120 init success"); radioType = LR1120_RADIO; @@ -456,11 +449,11 @@ bool initLoRa() #if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1 if (!rIf) { - rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN); + rIf = std::unique_ptr( + new LR1121Interface(loraHal, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN)); if (!rIf->init()) { LOG_WARN("No LR1121 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("LR1121 init success"); radioType = LR1121_RADIO; @@ -470,11 +463,10 @@ bool initLoRa() #if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 if (!rIf) { - rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY); + rIf = std::unique_ptr(new SX1280Interface(loraHal, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY)); if (!rIf->init()) { LOG_WARN("No SX1280 radio"); - delete rIf; - rIf = NULL; + rIf = nullptr; } else { LOG_INFO("SX1280 init success"); radioType = SX1280_RADIO; @@ -496,7 +488,7 @@ bool initLoRa() rebootAtMsec = millis() + 5000; } } - return rIf != nullptr; + return rIf; } void initRegion() diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index cb092bc6d..1fe3dd7b0 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -6,6 +6,7 @@ #include "PointerQueue.h" #include "airtime.h" #include "error.h" +#include // Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file. typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig; @@ -279,7 +280,7 @@ class RadioInterface } }; -bool initLoRa(); +std::unique_ptr initLoRa(); /// Debug printing for packets void printPacket(const char *prefix, const meshtastic_MeshPacket *p); diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 80e51b8bc..af6ab30c1 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -453,8 +453,11 @@ void RadioLibInterface::handleReceiveInterrupt() } #endif if (state != RADIOLIB_ERR_NONE) { - LOG_ERROR("Ignore received packet due to error=%d (maybe to=0x%08x, from=0x%08x, flags=0x%02x)", state, - radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags); + // Log PacketHeader similar to RadioInterface::printPacket so we can try to match RX errors to other packets in the logs. + LOG_ERROR("Ignore received packet due to error=%d (maybe id=0x%08x fr=0x%08x to=0x%08x flags=0x%02x rxSNR=%g rxRSSI=%i " + "nextHop=0x%x relay=0x%x)", + state, radioBuffer.header.id, radioBuffer.header.from, radioBuffer.header.to, radioBuffer.header.flags, + iface->getSNR(), lround(iface->getRSSI()), radioBuffer.header.next_hop, radioBuffer.header.relay_node); rxBad++; airTime->logAirtime(RX_ALL_LOG, rxMsec); @@ -514,6 +517,8 @@ void RadioLibInterface::handleReceiveInterrupt() void RadioLibInterface::startReceive() { + // Note the updated timestamp, to avoid unneeded AGC resets + last_listen = millis(); isReceiving = true; powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index dbe6f4f39..dbb5e5802 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -8,6 +8,7 @@ #include "PointerQueue.h" #include "RadioInterface.h" #include "concurrency/OSThread.h" +#include /** * A mesh aware router that supports multiple interfaces. @@ -20,7 +21,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory PointerQueue fromRadioQueue; protected: - RadioInterface *iface = NULL; + std::unique_ptr iface = nullptr; public: /** @@ -32,7 +33,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory /** * Currently we only allow one interface, that may change in the future */ - void addInterface(RadioInterface *_iface) { iface = _iface; } + void addInterface(std::unique_ptr _iface) { iface = std::move(_iface); } /** * do idle processing diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 20026767e..2d9230a21 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -27,7 +27,7 @@ int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) /** * Read any rx chars from the link and call handleRecStream */ -int32_t StreamAPI::readStream(char *buf, uint16_t bufLen) +int32_t StreamAPI::readStream(const char *buf, uint16_t bufLen) { if (bufLen < 1) { // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time @@ -56,7 +56,7 @@ void StreamAPI::writeStream() } } -int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen) +int32_t StreamAPI::handleRecStream(const char *buf, uint16_t bufLen) { uint16_t index = 0; while (bufLen > index) { // Currently we never want to block diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 4ca2c197f..97e231f23 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -57,8 +57,8 @@ class StreamAPI : public PhoneAPI * Read any rx chars from the link and call handleToRadio */ int32_t readStream(); - int32_t readStream(char *buf, uint16_t bufLen); - int32_t handleRecStream(char *buf, uint16_t bufLen); + int32_t readStream(const char *buf, uint16_t bufLen); + int32_t handleRecStream(const char *buf, uint16_t bufLen); /** * call getFromRadio() and deliver encapsulated packets to the Stream diff --git a/src/mesh/aes-ccm.cpp b/src/mesh/aes-ccm.cpp index 420d80e9a..5ed7ff928 100644 --- a/src/mesh/aes-ccm.cpp +++ b/src/mesh/aes-ccm.cpp @@ -33,7 +33,7 @@ static int constant_time_compare(const void *a_, const void *b_, size_t len) d |= (a[i] ^ b[i]); } /* Constant time bit arithmetic to convert d > 0 to -1 and d = 0 to 0. */ - return (1 & ((d - 1) >> 8)) - 1; + return (1 & (((unsigned int)d - 1) >> 8)) - 1; } static void WPA_PUT_BE16(uint8_t *a, uint16_t val) diff --git a/src/mesh/api/PacketAPI.h b/src/mesh/api/PacketAPI.h index fc08ab209..357eb05c2 100644 --- a/src/mesh/api/PacketAPI.h +++ b/src/mesh/api/PacketAPI.h @@ -17,7 +17,7 @@ class PacketAPI : public PhoneAPI, public concurrency::OSThread virtual int32_t runOnce(); protected: - PacketAPI(PacketServer *_server); + explicit PacketAPI(PacketServer *_server); // Check the current underlying physical queue to see if the client is fetching packets bool checkIsConnected() override; diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp index 01d3fa910..d0bd09800 100644 --- a/src/mesh/generated/meshtastic/admin.pb.cpp +++ b/src/mesh/generated/meshtastic/admin.pb.cpp @@ -36,6 +36,9 @@ PB_BIND(meshtastic_SCD4X_config, meshtastic_SCD4X_config, AUTO) PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO) +PB_BIND(meshtastic_SCD30_config, meshtastic_SCD30_config, AUTO) + + diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 336510ec3..3dd92997d 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -206,6 +206,27 @@ typedef struct _meshtastic_SEN5X_config { bool set_one_shot_mode; } meshtastic_SEN5X_config; +typedef struct _meshtastic_SCD30_config { + /* Set Automatic self-calibration enabled */ + bool has_set_asc; + bool set_asc; + /* Recalibration target CO2 concentration in ppm (FRC or ASC) */ + bool has_set_target_co2_conc; + uint32_t set_target_co2_conc; + /* Reference temperature in degC */ + bool has_set_temperature; + float set_temperature; + /* Altitude of sensor in meters above sea level. 0 - 3000m (overrides ambient pressure) */ + bool has_set_altitude; + uint32_t set_altitude; + /* Power mode for sensor (true for low power, false for normal) */ + bool has_set_measurement_interval; + uint32_t set_measurement_interval; + /* Perform a factory reset of the sensor */ + bool has_soft_reset; + bool soft_reset; +} meshtastic_SCD30_config; + typedef struct _meshtastic_SensorConfig { /* SCD4X CO2 Sensor configuration */ bool has_scd4x_config; @@ -213,6 +234,9 @@ typedef struct _meshtastic_SensorConfig { /* SEN5X PM Sensor configuration */ bool has_sen5x_config; meshtastic_SEN5X_config sen5x_config; + /* SCD30 CO2 Sensor configuration */ + bool has_scd30_config; + meshtastic_SCD30_config scd30_config; } meshtastic_SensorConfig; typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t; @@ -400,6 +424,7 @@ extern "C" { + /* Initializer values for message structs */ #define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} @@ -408,9 +433,10 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0} #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} -#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default} +#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default, false, meshtastic_SCD30_config_init_default} #define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_SEN5X_config_init_default {false, 0, false, 0} +#define meshtastic_SCD30_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}} @@ -418,9 +444,10 @@ extern "C" { #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0} #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} -#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero} +#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero, false, meshtastic_SCD30_config_init_zero} #define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_SEN5X_config_init_zero {false, 0, false, 0} +#define meshtastic_SCD30_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_AdminMessage_InputEvent_event_code_tag 1 @@ -451,8 +478,15 @@ extern "C" { #define meshtastic_SCD4X_config_set_power_mode_tag 7 #define meshtastic_SEN5X_config_set_temperature_tag 1 #define meshtastic_SEN5X_config_set_one_shot_mode_tag 2 +#define meshtastic_SCD30_config_set_asc_tag 1 +#define meshtastic_SCD30_config_set_target_co2_conc_tag 2 +#define meshtastic_SCD30_config_set_temperature_tag 3 +#define meshtastic_SCD30_config_set_altitude_tag 4 +#define meshtastic_SCD30_config_set_measurement_interval_tag 5 +#define meshtastic_SCD30_config_soft_reset_tag 6 #define meshtastic_SensorConfig_scd4x_config_tag 1 #define meshtastic_SensorConfig_sen5x_config_tag 2 +#define meshtastic_SensorConfig_scd30_config_tag 3 #define meshtastic_AdminMessage_get_channel_request_tag 1 #define meshtastic_AdminMessage_get_channel_response_tag 2 #define meshtastic_AdminMessage_get_owner_request_tag 3 @@ -642,11 +676,13 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4) #define meshtastic_SensorConfig_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) +X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, scd30_config, 3) #define meshtastic_SensorConfig_CALLBACK NULL #define meshtastic_SensorConfig_DEFAULT NULL #define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config #define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config +#define meshtastic_SensorConfig_scd30_config_MSGTYPE meshtastic_SCD30_config #define meshtastic_SCD4X_config_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \ @@ -665,6 +701,16 @@ X(a, STATIC, OPTIONAL, BOOL, set_one_shot_mode, 2) #define meshtastic_SEN5X_config_CALLBACK NULL #define meshtastic_SEN5X_config_DEFAULT NULL +#define meshtastic_SCD30_config_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \ +X(a, STATIC, OPTIONAL, UINT32, set_target_co2_conc, 2) \ +X(a, STATIC, OPTIONAL, FLOAT, set_temperature, 3) \ +X(a, STATIC, OPTIONAL, UINT32, set_altitude, 4) \ +X(a, STATIC, OPTIONAL, UINT32, set_measurement_interval, 5) \ +X(a, STATIC, OPTIONAL, BOOL, soft_reset, 6) +#define meshtastic_SCD30_config_CALLBACK NULL +#define meshtastic_SCD30_config_DEFAULT NULL + extern const pb_msgdesc_t meshtastic_AdminMessage_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg; extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg; @@ -675,6 +721,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg; extern const pb_msgdesc_t meshtastic_SensorConfig_msg; extern const pb_msgdesc_t meshtastic_SCD4X_config_msg; extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; +extern const pb_msgdesc_t meshtastic_SCD30_config_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg @@ -687,6 +734,7 @@ extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; #define meshtastic_SensorConfig_fields &meshtastic_SensorConfig_msg #define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg #define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg +#define meshtastic_SCD30_config_fields &meshtastic_SCD30_config_msg /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size @@ -696,9 +744,10 @@ extern const pb_msgdesc_t meshtastic_SEN5X_config_msg; #define meshtastic_HamParameters_size 31 #define meshtastic_KeyVerificationAdmin_size 25 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496 +#define meshtastic_SCD30_config_size 27 #define meshtastic_SCD4X_config_size 29 #define meshtastic_SEN5X_config_size 7 -#define meshtastic_SensorConfig_size 40 +#define meshtastic_SensorConfig_size 69 #define meshtastic_SharedContact_size 127 #ifdef __cplusplus diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index 7c7ae457a..8c34cd84c 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -109,7 +109,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* STH21 Temperature and R. Humidity sensor */ meshtastic_TelemetrySensorType_SHT21 = 47, /* Sensirion STC31 CO2 sensor */ - meshtastic_TelemetrySensorType_STC31 = 48 + meshtastic_TelemetrySensorType_STC31 = 48, + /* SCD30 CO2, humidity, temperature sensor */ + meshtastic_TelemetrySensorType_SCD30 = 49 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -487,8 +489,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_STC31 -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_STC31+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SCD30 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SCD30+1)) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index ea8d6af8e..281ece464 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -9,7 +9,6 @@ #if HAS_WIFI #include "mesh/wifi/WiFiAPClient.h" #endif -#include "Led.h" #include "SPILock.h" #include "power.h" #include "serialization/JSON.h" @@ -92,7 +91,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload); ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks); - ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED); ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport); ResourceNode *nodeJsonNodes = new ResourceNode("/json/nodes", "GET", &handleNodes); ResourceNode *nodeJsonFsBrowseStatic = new ResourceNode("/json/fs/browse/static", "GET", &handleFsBrowseStatic); @@ -110,7 +108,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) secureServer->registerNode(nodeRestart); secureServer->registerNode(nodeFormUpload); secureServer->registerNode(nodeJsonScanNetworks); - secureServer->registerNode(nodeJsonBlinkLED); secureServer->registerNode(nodeJsonFsBrowseStatic); secureServer->registerNode(nodeJsonDelete); secureServer->registerNode(nodeJsonReport); @@ -133,7 +130,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) insecureServer->registerNode(nodeRestart); insecureServer->registerNode(nodeFormUpload); insecureServer->registerNode(nodeJsonScanNetworks); - insecureServer->registerNode(nodeJsonBlinkLED); insecureServer->registerNode(nodeJsonFsBrowseStatic); insecureServer->registerNode(nodeJsonDelete); insecureServer->registerNode(nodeJsonReport); @@ -627,7 +623,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) } // Helper lambda to create JSON array and clean up memory properly - auto createJSONArrayFromLog = [](uint32_t *logArray, int count) -> JSONValue * { + auto createJSONArrayFromLog = [](const uint32_t *logArray, int count) -> JSONValue * { JSONArray tempArray; for (int i = 0; i < count; i++) { tempArray.push_back(new JSONValue((int)logArray[i])); @@ -904,45 +900,6 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res) webServerThread->requestRestart = (millis() / 1000) + 5; } -void handleBlinkLED(HTTPRequest *req, HTTPResponse *res) -{ - res->setHeader("Content-Type", "application/json"); - res->setHeader("Access-Control-Allow-Origin", "*"); - res->setHeader("Access-Control-Allow-Methods", "POST"); - - ResourceParameters *params = req->getParams(); - std::string blink_target; - - if (!params->getQueryParameter("blink_target", blink_target)) { - // if no blink_target was supplied in the URL parameters of the - // POST request, then assume we should blink the LED - blink_target = "LED"; - } - - if (blink_target == "LED") { - uint8_t count = 10; - while (count > 0) { - ledBlink.set(true); - delay(50); - ledBlink.set(false); - delay(50); - count = count - 1; - } - } else { -#if HAS_SCREEN - if (screen) - screen->blink(); -#endif - } - - JSONObject jsonObjOuter; - jsonObjOuter["status"] = new JSONValue("ok"); - JSONValue *value = new JSONValue(jsonObjOuter); - std::string jsonString = value->Stringify(); - res->print(jsonString.c_str()); - delete value; -} - void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) { res->setHeader("Content-Type", "application/json"); diff --git a/src/mesh/http/ContentHandler.h b/src/mesh/http/ContentHandler.h index 91cad3359..6efdb59b7 100644 --- a/src/mesh/http/ContentHandler.h +++ b/src/mesh/http/ContentHandler.h @@ -11,7 +11,6 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res); void handleScanNetworks(HTTPRequest *req, HTTPResponse *res); void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res); void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res); -void handleBlinkLED(HTTPRequest *req, HTTPResponse *res); void handleReport(HTTPRequest *req, HTTPResponse *res); void handleNodes(HTTPRequest *req, HTTPResponse *res); void handleUpdateFs(HTTPRequest *req, HTTPResponse *res); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 1fda9bf13..419d2b773 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -391,7 +391,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta node->has_device_metrics = false; node->has_position = false; node->user.public_key.size = 0; - node->user.public_key.bytes[0] = 0; + memset(node->user.public_key.bytes, 0, sizeof(node->user.public_key.bytes)); saveChanges(SEGMENT_NODEDATABASE, false); } break; @@ -643,12 +643,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) accelerometerThread->enabled = true; accelerometerThread->start(); } -#endif -#ifdef LED_PIN - // Turn LED off if heartbeat by config - if (c.payload_variant.device.led_heartbeat_disabled) { - digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON); - } #endif if (config.device.button_gpio == c.payload_variant.device.button_gpio && config.device.buzzer_gpio == c.payload_variant.device.buzzer_gpio && @@ -905,10 +899,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { + bool shouldReboot = true; // If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth // Otherwise, disable Bluetooth to prevent the phone from interfering with the config - if (!hasOpenEditTransaction && - !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) { + if (!hasOpenEditTransaction && !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, + meshtastic_ModuleConfig_serial_tag, meshtastic_ModuleConfig_statusmessage_tag)) { disableBluetooth(); } @@ -1000,8 +995,14 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) moduleConfig.has_paxcounter = true; moduleConfig.paxcounter = c.payload_variant.paxcounter; break; + case meshtastic_ModuleConfig_statusmessage_tag: + LOG_INFO("Set module config: StatusMessage"); + moduleConfig.has_statusmessage = true; + moduleConfig.statusmessage = c.payload_variant.statusmessage; + shouldReboot = false; + break; } - saveChanges(SEGMENT_MODULECONFIG); + saveChanges(SEGMENT_MODULECONFIG, shouldReboot); return true; } @@ -1180,6 +1181,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter; break; + case meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG: + LOG_INFO("Get module config: StatusMessage"); + res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag; + res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage; + break; } // NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior. diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 7d7b3cdb1..c7eb1b15b 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -1209,14 +1209,13 @@ int32_t CannedMessageModule::runOnce() this->cursor = 0; // Tell Screen to jump straight to the TextMessage frame - UIFrameEvent e; e.action = UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE; this->notifyObservers(&e); // Now deactivate this module this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - return INT32_MAX; // don’t fall back into canned list + return INT32_MAX; // don't fall back into canned list } else { this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } @@ -1237,14 +1236,13 @@ int32_t CannedMessageModule::runOnce() this->cursor = 0; // Tell Screen to jump straight to the TextMessage frame - UIFrameEvent e; e.action = UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE; this->notifyObservers(&e); // Now deactivate this module this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; - return INT32_MAX; // don’t fall back into canned list + return INT32_MAX; // don't fall back into canned list } } else { this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; @@ -1255,11 +1253,10 @@ int32_t CannedMessageModule::runOnce() this->freetext = ""; this->cursor = 0; - UIFrameEvent e; e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; this->notifyObservers(&e); - // Immediately stop, don’t linger on canned screen + // Immediately stop, don't linger on canned screen return INT32_MAX; } // Highlight [Select Destination] initially when entering the message list @@ -2070,7 +2067,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st // Draw lines with emotes int rowHeight = FONT_HEIGHT_SMALL; int yLine = inputY; - for (auto &line : lines) { + for (const auto &line : lines) { int nextX = x; for (const auto &token : line) { if (token.first) { diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 5e6985bdf..cc7124f0e 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -24,24 +24,8 @@ #include "mesh/generated/meshtastic/rtttl.pb.h" #include -#ifdef HAS_NCP5623 -#include -#endif - -#ifdef HAS_LP5562 -#include -#endif - -#ifdef HAS_NEOPIXEL -#include -#endif - -#ifdef UNPHONE -#include "unPhone.h" -extern unPhone unphone; -#endif - #if defined(HAS_RGB_LED) +#include "AmbientLightingThread.h" uint8_t red = 0; uint8_t green = 0; uint8_t blue = 0; @@ -123,32 +107,6 @@ int32_t ExternalNotificationModule::runOnce() green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7 blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7 white = (colorState & 12) ? brightnessValues[brightnessIndex] : 0; -#ifdef HAS_NCP5623 - if (rgb_found.type == ScanI2C::NCP5623) { - rgb.setColor(red, green, blue); - } -#endif -#ifdef HAS_LP5562 - if (rgb_found.type == ScanI2C::LP5562) { - rgbw.setColor(red, green, blue, white); - } -#endif -#ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic - analogWrite(RGBLED_GREEN, 255 - green); - analogWrite(RGBLED_BLUE, 255 - blue); -#elif defined(RGBLED_RED) - analogWrite(RGBLED_RED, red); - analogWrite(RGBLED_GREEN, green); - analogWrite(RGBLED_BLUE, blue); -#endif -#ifdef HAS_NEOPIXEL - pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); - pixels.show(); -#endif -#ifdef UNPHONE - unphone.rgb(red, green, blue); -#endif if (ascending) { // fade in brightnessIndex++; if (brightnessIndex == (sizeof(brightnessValues) - 1)) { @@ -255,34 +213,9 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on) blue = 0; white = 0; } + ambientLightingThread->setLighting(moduleConfig.ambient_lighting.current, red, green, blue); #endif -#ifdef HAS_NCP5623 - if (rgb_found.type == ScanI2C::NCP5623) { - rgb.setColor(red, green, blue); - } -#endif -#ifdef HAS_LP5562 - if (rgb_found.type == ScanI2C::LP5562) { - rgbw.setColor(red, green, blue, white); - } -#endif -#ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic - analogWrite(RGBLED_GREEN, 255 - green); - analogWrite(RGBLED_BLUE, 255 - blue); -#elif defined(RGBLED_RED) - analogWrite(RGBLED_RED, red); - analogWrite(RGBLED_GREEN, green); - analogWrite(RGBLED_BLUE, blue); -#endif -#ifdef HAS_NEOPIXEL - pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT); - pixels.show(); -#endif -#ifdef UNPHONE - unphone.rgb(red, green, blue); -#endif #ifdef HAS_DRV2605 if (on) { drv.go(); @@ -407,33 +340,6 @@ ExternalNotificationModule::ExternalNotificationModule() LOG_INFO("Use Pin %i in PWM mode", config.device.buzzer_gpio); } } -#ifdef HAS_NCP5623 - if (rgb_found.type == ScanI2C::NCP5623) { - rgb.begin(); - rgb.setCurrent(10); - } -#endif -#ifdef HAS_LP5562 - if (rgb_found.type == ScanI2C::LP5562) { - rgbw.begin(); - rgbw.setCurrent(20); - } -#endif -#ifdef RGBLED_RED - pinMode(RGBLED_RED, OUTPUT); // set up the RGB led pins - pinMode(RGBLED_GREEN, OUTPUT); - pinMode(RGBLED_BLUE, OUTPUT); -#endif -#ifdef RGBLED_CA - analogWrite(RGBLED_RED, 255); // with a common anode type, logic is reversed - analogWrite(RGBLED_GREEN, 255); // so we want to initialise with lights off - analogWrite(RGBLED_BLUE, 255); -#endif -#ifdef HAS_NEOPIXEL - pixels.begin(); // Initialise the pixel(s) - pixels.clear(); // Set all pixel colors to 'off' - pixels.setBrightness(moduleConfig.ambient_lighting.current); -#endif } else { LOG_INFO("External Notification Module Disabled"); disable(); @@ -461,7 +367,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } } - meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); + const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex()); // If we receive a broadcast message, apply channel mute setting diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index f667f7be9..94b021360 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -5,6 +5,11 @@ #include "configuration.h" #include "input/InputBroker.h" +#ifdef HAS_RGB_LED +#include "AmbientLightingThread.h" +extern AmbientLightingThread *ambientLightingThread; +#endif + #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) #include #else diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index e8da8e983..0708afe82 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -1,9 +1,12 @@ #include "configuration.h" #if !MESHTASTIC_EXCLUDE_INPUTBROKER #include "buzz/BuzzerFeedbackThread.h" -#include "modules/StatusLEDModule.h" #include "modules/SystemCommandsModule.h" #endif +#include "modules/StatusLEDModule.h" +#if !MESHTASTIC_EXCLUDE_REPLYBOT +#include "ReplyBotModule.h" +#endif #if !MESHTASTIC_EXCLUDE_PKI #include "KeyVerificationModule.h" #endif @@ -90,6 +93,9 @@ #if !MESHTASTIC_EXCLUDE_DROPZONE #include "modules/DropzoneModule.h" #endif +#if !MESHTASTIC_EXCLUDE_STATUS +#include "modules/StatusMessageModule.h" +#endif #if defined(HAS_HARDWARE_WATCHDOG) #include "watchdog/watchdogThread.h" @@ -106,10 +112,10 @@ void setupModules() buzzerFeedbackThread = new BuzzerFeedbackThread(); } #endif -#if defined(LED_CHARGE) || defined(LED_PAIRING) statusLEDModule = new StatusLEDModule(); +#if !MESHTASTIC_EXCLUDE_REPLYBOT + new ReplyBotModule(); #endif - #if !MESHTASTIC_EXCLUDE_ADMIN adminModule = new AdminModule(); #endif @@ -150,6 +156,9 @@ void setupModules() #if !MESHTASTIC_EXCLUDE_DROPZONE dropzoneModule = new DropzoneModule(); #endif +#if !MESHTASTIC_EXCLUDE_STATUS + statusMessageModule = new StatusMessageModule(); +#endif #if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE new GenericThreadModule(); #endif diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index a568505ae..f947a5ac0 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -148,7 +148,7 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() // Strip the public key if the user is licensed if (u.is_licensed && u.public_key.size > 0) { - u.public_key.bytes[0] = 0; + memset(u.public_key.bytes, 0, sizeof(u.public_key.bytes)); u.public_key.size = 0; } diff --git a/src/modules/PowerStressModule.cpp b/src/modules/PowerStressModule.cpp index d487fe6fc..1c073a10a 100644 --- a/src/modules/PowerStressModule.cpp +++ b/src/modules/PowerStressModule.cpp @@ -1,5 +1,4 @@ #include "PowerStressModule.h" -#include "Led.h" #include "MeshService.h" #include "NodeDB.h" #include "PowerMon.h" @@ -78,10 +77,12 @@ int32_t PowerStressModule::runOnce() switch (p.cmd) { case meshtastic_PowerStressMessage_Opcode_LED_ON: - ledForceOn.set(true); + // FIXME - implement + // ledForceOn.set(true); break; case meshtastic_PowerStressMessage_Opcode_LED_OFF: - ledForceOn.set(false); + // FIXME - implement + // ledForceOn.set(false); break; case meshtastic_PowerStressMessage_Opcode_GPS_ON: // FIXME - implement diff --git a/src/modules/ReplyBotModule.cpp b/src/modules/ReplyBotModule.cpp new file mode 100644 index 000000000..d391bf093 --- /dev/null +++ b/src/modules/ReplyBotModule.cpp @@ -0,0 +1,183 @@ +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_REPLYBOT +/* + * ReplyBotModule.cpp + * + * This module implements a simple reply bot for the Meshtastic firmware. It listens for + * specific text commands ("/ping", "/hello" and "/test") delivered either via a direct + * message (DM) or a broadcast on the primary channel. When a supported command is + * received the bot responds with a short status message that includes the hop count + * (minimum number of relays), RSSI and SNR of the received packet. To avoid spamming + * the network it enforces a per‑sender cooldown between responses. By default the + * module is disabled. See the official firmware documentation for guidance on adding modules. + * To enable this module, set `#undef MESHTASTIC_EXCLUDE_REPLYBOT` in your variant.h file. + */ + +#include "Channels.h" +#include "MeshService.h" +#include "NodeDB.h" +#include "ReplyBotModule.h" +#include "mesh/MeshTypes.h" + +#include +#include +#include + +// +// Rate limiting data structures +// +// Each sender is tracked in a small ring buffer. When a message arrives from a +// sender we check the last time we responded to them. If the difference is +// less than the configured cooldown (different values for DM vs broadcast) +// the message is ignored; otherwise we update the last response time and +// proceed with replying. + +struct ReplyBotCooldownEntry { + uint32_t from = 0; + uint32_t lastMs = 0; +}; + +static constexpr uint8_t REPLYBOT_COOLDOWN_SLOTS = 8; // ring buffer size +static constexpr uint32_t REPLYBOT_DM_COOLDOWN_MS = 15 * 1000; // 15 seconds for DMs +static constexpr uint32_t REPLYBOT_LF_COOLDOWN_MS = 60 * 1000; // 60 seconds for LongFast broadcasts + +static ReplyBotCooldownEntry replybotCooldown[REPLYBOT_COOLDOWN_SLOTS]; +static uint8_t replybotCooldownIdx = 0; + +// Return true if a reply should be rate‑limited for this sender, updating the +// entry table as needed. +static bool replybotRateLimited(uint32_t from, uint32_t cooldownMs) +{ + const uint32_t now = millis(); + for (auto &e : replybotCooldown) { + if (e.from == from) { + // Found existing entry; check if cooldown expired + if ((uint32_t)(now - e.lastMs) < cooldownMs) { + return true; + } + e.lastMs = now; + return false; + } + } + // No entry found – insert new sender into the ring + replybotCooldown[replybotCooldownIdx].from = from; + replybotCooldown[replybotCooldownIdx].lastMs = now; + replybotCooldownIdx = (replybotCooldownIdx + 1) % REPLYBOT_COOLDOWN_SLOTS; + return false; +} + +// Constructor – registers a single text port and marks the module promiscuous +// so that broadcast messages on the primary channel are visible. +ReplyBotModule::ReplyBotModule() : SinglePortModule("replybot", meshtastic_PortNum_TEXT_MESSAGE_APP) +{ + isPromiscuous = true; +} + +void ReplyBotModule::setup() +{ + // In future we may add a protobuf configuration; for now the module is + // always enabled when compiled in. +} + +// Determine whether we want to process this packet. We only care about +// plain text messages addressed to our port. +bool ReplyBotModule::wantPacket(const meshtastic_MeshPacket *p) +{ + return (p && p->decoded.portnum == ourPortNum); +} + +ProcessMessage ReplyBotModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + // Accept only direct messages to us or broadcasts on the Primary channel + // (regardless of modem preset: LongFast, MediumFast, etc). + + const uint32_t ourNode = nodeDB->getNodeNum(); + const bool isDM = (mp.to == ourNode); + const bool isPrimaryChannel = (mp.channel == channels.getPrimaryIndex()) && isBroadcast(mp.to); + if (!isDM && !isPrimaryChannel) { + return ProcessMessage::CONTINUE; + } + + // Ignore empty payloads + if (mp.decoded.payload.size == 0) { + return ProcessMessage::CONTINUE; + } + + // Copy payload into a null‑terminated buffer + char buf[260]; + memset(buf, 0, sizeof(buf)); + size_t n = mp.decoded.payload.size; + if (n > sizeof(buf) - 1) + n = sizeof(buf) - 1; + memcpy(buf, mp.decoded.payload.bytes, n); + + // React only to supported slash commands + if (!isCommand(buf)) { + return ProcessMessage::CONTINUE; + } + + // Apply rate limiting per sender depending on DM/broadcast + const uint32_t cooldownMs = isDM ? REPLYBOT_DM_COOLDOWN_MS : REPLYBOT_LF_COOLDOWN_MS; + if (replybotRateLimited(mp.from, cooldownMs)) { + return ProcessMessage::CONTINUE; + } + + // Compute hop count indicator – if the relay_node is non‑zero we know + // there was at least one relay. Some firmware builds support a hop_start + // field which could be used for more accurate counts, but here we use + // the available relay_node flag only. + // int hopsAway = mp.hop_start - mp.hop_limit; + int hopsAway = getHopsAway(mp); + + // Normalize RSSI: if positive adjust down by 200 to align with typical values + int rssi = mp.rx_rssi; + if (rssi > 0) { + rssi -= 200; + } + float snr = mp.rx_snr; + + // Build the reply message and send it back via DM + char reply[96]; + snprintf(reply, sizeof(reply), "🎙️ Mic Check : %d Hops away | RSSI %d | SNR %.1f", hopsAway, rssi, snr); + sendDm(mp, reply); + return ProcessMessage::CONTINUE; +} + +// Check if the message starts with one of the supported commands. Leading +// whitespace is skipped and commands must be followed by end‑of‑string or +// whitespace. +bool ReplyBotModule::isCommand(const char *msg) const +{ + if (!msg) + return false; + while (*msg == ' ' || *msg == '\t') + msg++; + auto isEndOrSpace = [](char c) { return c == '\0' || std::isspace(static_cast(c)); }; + if (strncmp(msg, "/ping", 5) == 0 && isEndOrSpace(msg[5])) + return true; + if (strncmp(msg, "/hello", 6) == 0 && isEndOrSpace(msg[6])) + return true; + if (strncmp(msg, "/test", 5) == 0 && isEndOrSpace(msg[5])) + return true; + return false; +} + +// Send a direct message back to the originating node. +void ReplyBotModule::sendDm(const meshtastic_MeshPacket &rx, const char *text) +{ + if (!text) + return; + meshtastic_MeshPacket *p = allocDataPacket(); + p->to = rx.from; + p->channel = rx.channel; + p->want_ack = false; + p->decoded.want_response = false; + size_t len = strlen(text); + if (len > sizeof(p->decoded.payload.bytes)) { + len = sizeof(p->decoded.payload.bytes); + } + p->decoded.payload.size = len; + memcpy(p->decoded.payload.bytes, text, len); + service->sendToMesh(p); +} +#endif // MESHTASTIC_EXCLUDE_REPLYBOT \ No newline at end of file diff --git a/src/modules/ReplyBotModule.h b/src/modules/ReplyBotModule.h new file mode 100644 index 000000000..a5a8f6bb4 --- /dev/null +++ b/src/modules/ReplyBotModule.h @@ -0,0 +1,19 @@ +#pragma once +#include "configuration.h" +#if !MESHTASTIC_EXCLUDE_REPLYBOT +#include "SinglePortModule.h" +#include "mesh/generated/meshtastic/mesh.pb.h" + +class ReplyBotModule : public SinglePortModule +{ + public: + ReplyBotModule(); + void setup() override; + bool wantPacket(const meshtastic_MeshPacket *p) override; + ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + protected: + bool isCommand(const char *msg) const; + void sendDm(const meshtastic_MeshPacket &rx, const char *text); +}; +#endif // MESHTASTIC_EXCLUDE_REPLYBOT \ No newline at end of file diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index e7a405bdf..f828f4a16 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -13,15 +13,16 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule") { bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); powerStatusObserver.observe(&powerStatus->onNewStatus); +#if !MESHTASTIC_EXCLUDE_INPUTBROKER if (inputBroker) inputObserver.observe(inputBroker); +#endif } int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) { switch (arg->getStatusType()) { case STATUS_TYPE_POWER: { - meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg; if (powerStatus->getHasUSB() || powerStatus->getIsCharging()) { power_state = charging; if (powerStatus->getBatteryChargePercent() >= 100) { @@ -37,7 +38,6 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) break; } case STATUS_TYPE_BLUETOOTH: { - meshtastic::BluetoothStatus *bluetoothStatus = (meshtastic::BluetoothStatus *)arg; switch (bluetoothStatus->getConnectionState()) { case meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED: { ble_state = unpaired; @@ -62,19 +62,22 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg) } return 0; }; - +#if !MESHTASTIC_EXCLUDE_INPUTBROKER int StatusLEDModule::handleInputEvent(const InputEvent *event) { lastUserbuttonTime = millis(); return 0; } +#endif int32_t StatusLEDModule::runOnce() { my_interval = 1000; if (power_state == charging) { +#ifndef POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING CHARGE_LED_state = !CHARGE_LED_state; +#endif } else if (power_state == charged) { CHARGE_LED_state = LED_STATE_ON; } else if (power_state == critical) { @@ -88,13 +91,19 @@ int32_t StatusLEDModule::runOnce() my_interval = 250; if (POWER_LED_starttime + 2000 < millis()) { doing_fast_blink = false; + CHARGE_LED_state = LED_STATE_OFF; } - } else { - CHARGE_LED_state = LED_STATE_OFF; } + } - } else { - CHARGE_LED_state = LED_STATE_OFF; + if (power_state != charging && power_state != charged && !doing_fast_blink) { + if (CHARGE_LED_state == LED_STATE_ON) { + CHARGE_LED_state = LED_STATE_OFF; + my_interval = 999; + } else { + CHARGE_LED_state = LED_STATE_ON; + my_interval = 1; + } } if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis() || doing_fast_blink) { @@ -112,6 +121,11 @@ int32_t StatusLEDModule::runOnce() PAIRING_LED_state = LED_STATE_ON; } + // Override if disabled in config + if (config.device.led_heartbeat_disabled) { + CHARGE_LED_state = LED_STATE_OFF; + } +#ifdef Battery_LED_1 bool chargeIndicatorLED1 = LED_STATE_OFF; bool chargeIndicatorLED2 = LED_STATE_OFF; bool chargeIndicatorLED3 = LED_STATE_OFF; @@ -126,14 +140,38 @@ int32_t StatusLEDModule::runOnce() if (powerStatus && powerStatus->getBatteryChargePercent() >= 75) chargeIndicatorLED4 = LED_STATE_ON; } +#endif -#ifdef LED_CHARGE - digitalWrite(LED_CHARGE, CHARGE_LED_state); +#if defined(HAS_PMU) + if (pmu_found && PMU) { + // blink the axp led + PMU->setChargingLedMode(CHARGE_LED_state ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); + } +#endif + +#ifdef PCA_LED_POWER + io.digitalWrite(PCA_LED_POWER, CHARGE_LED_state); +#endif +#ifdef PCA_LED_ENABLE + io.digitalWrite(PCA_LED_ENABLE, CHARGE_LED_state); +#endif +#ifdef LED_POWER + digitalWrite(LED_POWER, CHARGE_LED_state); #endif #ifdef LED_PAIRING digitalWrite(LED_PAIRING, PAIRING_LED_state); #endif +#ifdef RGB_LED_POWER + if (!config.device.led_heartbeat_disabled) { + if (CHARGE_LED_state == LED_STATE_ON) { + ambientLightingThread->setLighting(10, 255, 0, 0); + } else { + ambientLightingThread->setLighting(0, 0, 0, 0); + } + } +#endif + #ifdef Battery_LED_1 digitalWrite(Battery_LED_1, chargeIndicatorLED1); #endif @@ -149,3 +187,40 @@ int32_t StatusLEDModule::runOnce() return (my_interval); } + +void StatusLEDModule::setPowerLED(bool LEDon) +{ + +#if defined(HAS_PMU) + if (pmu_found && PMU) { + // blink the axp led + PMU->setChargingLedMode(LEDon ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF); + } +#endif + uint8_t ledState = LEDon ? LED_STATE_ON : LED_STATE_OFF; +#ifdef PCA_LED_POWER + io.digitalWrite(PCA_LED_POWER, ledState); +#endif +#ifdef PCA_LED_ENABLE + io.digitalWrite(PCA_LED_ENABLE, ledState); +#endif +#ifdef LED_POWER + digitalWrite(LED_POWER, ledState); +#endif +#ifdef LED_PAIRING + digitalWrite(LED_PAIRING, ledState); +#endif + +#ifdef Battery_LED_1 + digitalWrite(Battery_LED_1, ledState); +#endif +#ifdef Battery_LED_2 + digitalWrite(Battery_LED_2, ledState); +#endif +#ifdef Battery_LED_3 + digitalWrite(Battery_LED_3, ledState); +#endif +#ifdef Battery_LED_4 + digitalWrite(Battery_LED_4, ledState); +#endif +} diff --git a/src/modules/StatusLEDModule.h b/src/modules/StatusLEDModule.h index 98020cb32..972e26737 100644 --- a/src/modules/StatusLEDModule.h +++ b/src/modules/StatusLEDModule.h @@ -5,10 +5,14 @@ #include "PowerStatus.h" #include "concurrency/OSThread.h" #include "configuration.h" -#include "input/InputBroker.h" +#include "main.h" #include #include +#if !MESHTASTIC_EXCLUDE_INPUTBROKER +#include "input/InputBroker.h" +#endif + class StatusLEDModule : private concurrency::OSThread { bool slowTrack = false; @@ -17,8 +21,11 @@ class StatusLEDModule : private concurrency::OSThread StatusLEDModule(); int handleStatusUpdate(const meshtastic::Status *); - +#if !MESHTASTIC_EXCLUDE_INPUTBROKER int handleInputEvent(const InputEvent *arg); +#endif + + void setPowerLED(bool); protected: unsigned int my_interval = 1000; // interval in millisconds @@ -28,8 +35,10 @@ class StatusLEDModule : private concurrency::OSThread CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); CallbackObserver powerStatusObserver = CallbackObserver(this, &StatusLEDModule::handleStatusUpdate); +#if !MESHTASTIC_EXCLUDE_INPUTBROKER CallbackObserver inputObserver = CallbackObserver(this, &StatusLEDModule::handleInputEvent); +#endif private: bool CHARGE_LED_state = LED_STATE_OFF; @@ -50,3 +59,7 @@ class StatusLEDModule : private concurrency::OSThread }; extern StatusLEDModule *statusLEDModule; +#ifdef RGB_LED_POWER +#include "AmbientLightingThread.h" +extern AmbientLightingThread *ambientLightingThread; +#endif diff --git a/src/modules/StatusMessageModule.cpp b/src/modules/StatusMessageModule.cpp new file mode 100644 index 000000000..139a74d8e --- /dev/null +++ b/src/modules/StatusMessageModule.cpp @@ -0,0 +1,41 @@ +#if !MESHTASTIC_EXCLUDE_STATUS + +#include "StatusMessageModule.h" +#include "MeshService.h" +#include "ProtobufModule.h" + +StatusMessageModule *statusMessageModule; + +int32_t StatusMessageModule::runOnce() +{ + if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') { + // create and send message with the status message set + meshtastic_StatusMessage ourStatus = meshtastic_StatusMessage_init_zero; + strncpy(ourStatus.status, moduleConfig.statusmessage.node_status, sizeof(ourStatus.status)); + ourStatus.status[sizeof(ourStatus.status) - 1] = '\0'; // ensure null termination + meshtastic_MeshPacket *p = allocDataPacket(); + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), + meshtastic_StatusMessage_fields, &ourStatus); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + p->channel = 0; + service->sendToMesh(p); + } + + return 1000 * 12 * 60 * 60; +} + +ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp) +{ + if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { + meshtastic_StatusMessage incomingMessage; + if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields, + &incomingMessage)) { + LOG_INFO("Received a NodeStatus message %s", incomingMessage.status); + } + } + return ProcessMessage::CONTINUE; +} + +#endif \ No newline at end of file diff --git a/src/modules/StatusMessageModule.h b/src/modules/StatusMessageModule.h new file mode 100644 index 000000000..c9ff54018 --- /dev/null +++ b/src/modules/StatusMessageModule.h @@ -0,0 +1,35 @@ +#pragma once +#if !MESHTASTIC_EXCLUDE_STATUS +#include "SinglePortModule.h" +#include "configuration.h" + +class StatusMessageModule : public SinglePortModule, private concurrency::OSThread +{ + + public: + /** Constructor + * name is for debugging output + */ + StatusMessageModule() + : SinglePortModule("statusMessage", meshtastic_PortNum_NODE_STATUS_APP), concurrency::OSThread("StatusMessage") + { + if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') { + this->setInterval(2 * 60 * 1000); + } else { + this->setInterval(1000 * 12 * 60 * 60); + } + // TODO: If we have a string, set the initial delay (15 minutes maybe) + } + + virtual int32_t runOnce() override; + + protected: + /** Called to handle a particular incoming message + */ + virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; + + private: +}; + +extern StatusMessageModule *statusMessageModule; +#endif \ No newline at end of file diff --git a/src/modules/StoreForwardModule.cpp b/src/modules/StoreForwardModule.cpp index 023a1c798..135d9f6eb 100644 --- a/src/modules/StoreForwardModule.cpp +++ b/src/modules/StoreForwardModule.cpp @@ -131,9 +131,7 @@ void StoreForwardModule::historySend(uint32_t secAgo, uint32_t to) uint32_t StoreForwardModule::getNumAvailablePackets(NodeNum dest, uint32_t last_time) { uint32_t count = 0; - if (lastRequest.find(dest) == lastRequest.end()) { - lastRequest.emplace(dest, 0); - } + lastRequest.emplace(dest, 0); for (uint32_t i = lastRequest[dest]; i < this->packetHistoryTotalCount; i++) { if (this->packetHistory[i].time && (this->packetHistory[i].time > last_time)) { // Client is only interested in packets not from itself and only in broadcast packets or packets towards it. diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index cc1b54373..5ffe4d992 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -1,3 +1,4 @@ +#include "DebugConfiguration.h" #include "configuration.h" #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR @@ -25,6 +26,12 @@ #if __has_include() #include "Sensor/SCD4XSensor.h" #endif +#if __has_include() +#include "Sensor/SFA30Sensor.h" +#endif +#if __has_include() +#include "Sensor/SCD30Sensor.h" +#endif void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { @@ -50,6 +57,12 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::SCD4X); #endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SFA30); +#endif +#if __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SCD30); +#endif } int32_t AirQualityTelemetryModule::runOnce() @@ -71,7 +84,8 @@ int32_t AirQualityTelemetryModule::runOnce() } if (firstTime) { - // This is the first time the OSThread library has called this function, so do some setup + // This is the first time the OSThread library has called this function, so + // do some setup firstTime = false; if (moduleConfig.telemetry.air_quality_enabled) { @@ -221,6 +235,8 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3"); if (m.has_co2) entries.push_back("CO2: " + String(m.co2) + "ppm"); + if (m.has_form_formaldehyde) + entries.push_back("HCHO: " + String(m.form_formaldehyde) + "ppb"); // === Show first available metric on top-right of first line === if (!entries.empty()) { @@ -256,17 +272,19 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) const char *sender = getSenderShortName(mp); - LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender, - t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard, - t->variant.air_quality_metrics.pm100_standard); + if (t->variant.air_quality_metrics.has_pm10_standard) + LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, " + "pm100_standard=%i", + sender, t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard, + t->variant.air_quality_metrics.pm100_standard); - // TODO - Decide what to do with these - // LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i", - // t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental, - // t->variant.air_quality_metrics.pm100_environmental); + if (t->variant.air_quality_metrics.has_co2) + LOG_INFO("CO2=%i, CO2_T=%.2f, CO2_H=%.2f", t->variant.air_quality_metrics.co2, + t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity); - LOG_INFO(" | CO2=%i, CO2_T=%f, CO2_H=%f", t->variant.air_quality_metrics.co2, - t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity); + if (t->variant.air_quality_metrics.has_form_formaldehyde) + LOG_INFO("HCHO=%.2f, HCHO_T=%.2f, HCHO_H=%.2f", t->variant.air_quality_metrics.form_formaldehyde, + t->variant.air_quality_metrics.form_temperature, t->variant.air_quality_metrics.form_humidity); #endif // release previous packet before occupying a new spot if (lastMeasurementPacket != nullptr) @@ -354,10 +372,18 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) m.variant.air_quality_metrics.has_co2_humidity; if (hasAnyCO2) { - LOG_INFO("Send: co2=%i, co2_t=%f, co2_rh=%f", m.variant.air_quality_metrics.co2, + LOG_INFO("Send: co2=%i, co2_t=%.2f, co2_rh=%.2f", m.variant.air_quality_metrics.co2, m.variant.air_quality_metrics.co2_temperature, m.variant.air_quality_metrics.co2_humidity); } + bool hasAnyHCHO = m.variant.air_quality_metrics.has_form_formaldehyde || + m.variant.air_quality_metrics.has_form_temperature || m.variant.air_quality_metrics.has_form_humidity; + + if (hasAnyHCHO) { + LOG_INFO("Send: hcho=%.2f, hcho_t=%.2f, hcho_rh=%.2f", m.variant.air_quality_metrics.form_formaldehyde, + m.variant.air_quality_metrics.form_temperature, m.variant.air_quality_metrics.form_humidity); + } + meshtastic_MeshPacket *p = allocDataProtobuf(m); p->to = dest; p->decoded.want_response = false; @@ -426,4 +452,4 @@ AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule( return result; } -#endif \ No newline at end of file +#endif diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 2b88b74ba..197491f2d 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -64,8 +64,9 @@ class AirQualityTelemetryModule : private concurrency::OSThread, bool firstTime = true; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute + // uint32_t sendToPhoneIntervalMs = 1000; // Send to phone every minute uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; }; -#endif \ No newline at end of file +#endif diff --git a/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h index 37d909d71..b7029986b 100644 --- a/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h +++ b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h @@ -8,7 +8,7 @@ static std::forward_list sensors; -template void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type) +template void addSensor(const ScanI2C *i2cScanner, ScanI2C::DeviceType type) { ScanI2C::FoundDevice dev = i2cScanner->find(type); if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) { diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 3a1eb9532..c202028e1 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -8,6 +8,10 @@ #include "SPILock.h" #include "TelemetrySensor.h" +#if __has_include() +#include +#endif + BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} #if __has_include() @@ -96,8 +100,28 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.environment_metrics.temperature = bme680->readTemperature(); measurement->variant.environment_metrics.relative_humidity = bme680->readHumidity(); measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F; - measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0; + float gasRaw = bme680->readGas(); + measurement->variant.environment_metrics.gas_resistance = gasRaw / 1000.0; + + // IAQ approximation: humidity-compensated logarithmic mapping of gas resistance + // Gas sensor resistance drops with humidity; compensate to a 40% RH reference baseline + // Map compensated gas resistance (Ohms) to IAQ 0-500 using log-linear interpolation + // Clean air reference ~400 kOhm, polluted reference ~5 kOhm + if (gasRaw > 0.0f && !isfinite(gasRaw)) { + + static constexpr float LOG_UPPER = 12.899219f; // log(400k) + static constexpr float LOG_RANGE_INV = 1.0f / (12.899219f - 8.517193f); // 1 / (log(400k) - log(5k)) + measurement->variant.environment_metrics.has_iaq = true; + measurement->variant.environment_metrics.iaq = (uint16_t)(fminf( + fmaxf(((LOG_UPPER - + logf(fmaxf(gasRaw * expf(0.035f * (measurement->variant.environment_metrics.relative_humidity - 40.0f)), + 1.0f))) * + LOG_RANGE_INV) * + 500.0f, + 0.0f), + 500.0f)); + } #endif return true; } diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index eaeceb848..1134f04d9 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -28,7 +28,7 @@ class BME680Sensor : public TelemetrySensor #else using BME680Ptr = std::unique_ptr; - static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique(bus); } + static BME680Ptr makeBME680(TwoWire *bus) { return BME680Ptr(new Adafruit_BME680(bus)); } BME680Ptr bme680; #endif diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp index bc067c04c..e34b70a1f 100644 --- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp +++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp @@ -67,7 +67,7 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) #endif /* CAN_RECLOCK_I2C */ #endif /* PMSA003I_I2C_CLOCK_SPEED */ - _bus->requestFrom(_address, PMSA003I_FRAME_LENGTH); + _bus->requestFrom(_address, (uint8_t)PMSA003I_FRAME_LENGTH); if (_bus->available() < PMSA003I_FRAME_LENGTH) { LOG_WARN("%s read failed: incomplete data (%d bytes)", sensorName, _bus->available()); return false; @@ -86,7 +86,7 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) return false; } - auto read16 = [](uint8_t *data, uint8_t idx) -> uint16_t { return (data[idx] << 8) | data[idx + 1]; }; + auto read16 = [](const uint8_t *data, uint8_t idx) -> uint16_t { return (data[idx] << 8) | data[idx + 1]; }; computedChecksum = 0; @@ -138,6 +138,10 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement) measurement->variant.air_quality_metrics.has_particles_100um = true; measurement->variant.air_quality_metrics.particles_100um = read16(buffer, 26); + LOG_DEBUG("Got %s readings: pM1p0_standard=%u, pM2p5_standard=%u, pM10p0_standard=%u", sensorName, + measurement->variant.air_quality_metrics.pm10_standard, measurement->variant.air_quality_metrics.pm25_standard, + measurement->variant.air_quality_metrics.pm100_standard); + return true; } diff --git a/src/modules/Telemetry/Sensor/SCD30Sensor.cpp b/src/modules/Telemetry/Sensor/SCD30Sensor.cpp new file mode 100644 index 000000000..0478b6651 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SCD30Sensor.cpp @@ -0,0 +1,511 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../detect/reClockI2C.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SCD30Sensor.h" + +#define SCD30_NO_ERROR 0 + +SCD30Sensor::SCD30Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SCD30, "SCD30") {} + +bool SCD30Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + LOG_INFO("Init sensor: %s", sensorName); + + _bus = bus; + _address = dev->address.address; + +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + scd30.begin(*_bus, _address); + + if (!startMeasurement()) { + LOG_ERROR("%s: Failed to start periodic measurement", sensorName); +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + if (!getASC(ascActive)) { + LOG_WARN("%s: Could not determine ASC state", sensorName); + } + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + if (state == SCD30_MEASUREMENT) { + status = 1; + } else { + status = 0; + } + + initI2CSensor(); + + return true; +} + +bool SCD30Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + float co2, temperature, humidity; + +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + if (scd30.readMeasurementData(co2, temperature, humidity) != SCD30_NO_ERROR) { + LOG_ERROR("SCD30: Failed to read measurement data."); +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + if (co2 == 0) { + LOG_ERROR("SCD30: Invalid CO₂ reading."); + return false; + } + + measurement->variant.air_quality_metrics.has_co2 = true; + measurement->variant.air_quality_metrics.has_co2_temperature = true; + measurement->variant.air_quality_metrics.has_co2_humidity = true; + measurement->variant.air_quality_metrics.co2 = (uint32_t)co2; + measurement->variant.air_quality_metrics.co2_temperature = temperature; + measurement->variant.air_quality_metrics.co2_humidity = humidity; + + LOG_DEBUG("Got %s readings: co2=%u, co2_temp=%.2f, co2_hum=%.2f", sensorName, (uint32_t)co2, temperature, humidity); + + return true; +} + +bool SCD30Sensor::setMeasurementInterval(uint16_t measInterval) +{ + uint16_t error; + + LOG_INFO("%s: setting measurement interval at %us", sensorName, measInterval); + error = scd30.setMeasurementInterval(measInterval); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to set measurement interval. Error code: %u", sensorName, error); + return false; + } + + // Restart measuring so we don't need to wait the current interval to finish + // (useful when you come from very long intervals) + scd30.stopPeriodicMeasurement(); + scd30.startPeriodicMeasurement(0); + + getMeasurementInterval(measurementInterval); + return true; +} + +bool SCD30Sensor::getMeasurementInterval(uint16_t &measInterval) +{ + uint16_t error; + + LOG_INFO("%s: getting measurement interval", sensorName); + error = scd30.getMeasurementInterval(measInterval); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to get measurement interval. Error code: %u", sensorName, error); + return false; + } + + LOG_INFO("%s: measurement interval is %us", sensorName, measInterval); + + return true; +} + +/** + * @brief Start measurement mode + * @note This function should not change the clock + */ +bool SCD30Sensor::startMeasurement() +{ + uint16_t error; + + if (state == SCD30_MEASUREMENT) { + LOG_DEBUG("%s: Already in measurement mode", sensorName); + return true; + } + + error = scd30.startPeriodicMeasurement(0); + + if (error == SCD30_NO_ERROR) { + LOG_INFO("%s: Started measurement mode", sensorName); + + state = SCD30_MEASUREMENT; + return true; + } else { + LOG_ERROR("%s: Couldn't start measurement mode", sensorName); + return false; + } +} + +/** + * @brief Stop measurement mode + * @note This function should not change the clock + */ +bool SCD30Sensor::stopMeasurement() +{ + uint16_t error; + + error = scd30.stopPeriodicMeasurement(); + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to stop measurement", sensorName); + return false; + } + + state = SCD30_IDLE; + return true; +} + +bool SCD30Sensor::performFRC(uint16_t targetCO2) +{ + uint16_t error; + + LOG_INFO("%s: Issuing FRC. Ensure device has been working at least 3 minutes in stable target environment", sensorName); + + LOG_INFO("%s: Target CO2: %u ppm", sensorName, targetCO2); + error = scd30.forceRecalibration((uint16_t)targetCO2); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to perform forced recalibration.", sensorName); + return false; + } + + LOG_INFO("%s: FRC Correction successful.", sensorName); + + return true; +} + +bool SCD30Sensor::setASC(bool ascEnabled) +{ + uint16_t error; + + LOG_INFO("%s: %s ASC", sensorName, ascEnabled ? "Enabling" : "Disabling"); + + error = scd30.activateAutoCalibration((uint16_t)ascEnabled); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to send command.", sensorName); + return false; + } + + if (!getASC(ascActive)) { + LOG_ERROR("%s: Unable to check if ASC is enabled", sensorName); + return false; + } + + return true; +} + +bool SCD30Sensor::getASC(uint16_t &_ascActive) +{ + uint16_t error; + // LOG_INFO("%s: Getting ASC", sensorName); + + error = scd30.getAutoCalibrationStatus(_ascActive); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to send command.", sensorName); + return false; + } + + LOG_INFO("%s: ASC is %s", sensorName, _ascActive ? "enabled" : "disabled"); + + return true; +} + +/** + * @brief Set the temperature reference. Unit ℃. + * + * The on-board RH/T sensor is influenced by thermal self-heating of SCD30 + * and other electrical components. Design-in alters the thermal properties + * of SCD30 such that temperature and humidity offsets may occur when + * operating the sensor in end-customer devices. Compensation of those + * effects is achievable by writing the temperature offset found in + * continuous operation of the device into the sensor. Temperature offset + * value is saved in non-volatile memory. The last set value will be used + * for temperature offset compensation after repowering. + * + * @param[in] tempReference + * @note this function is certainly confusing and it's not recommended + */ +bool SCD30Sensor::setTemperature(float tempReference) +{ + uint16_t error; + uint16_t updatedTempOffset; + float tempOffset; + uint16_t _tempOffset; + float co2; + float temperature; + float humidity; + + if (tempReference == 100) { + // Requesting the value of 100 will restore the temperature offset + LOG_INFO("%s: Setting reference temperature at 0degC", sensorName); + _tempOffset = 0; + } else { + + LOG_INFO("%s: Setting reference temperature at: %.2f", sensorName, tempReference); + + error = scd30.readMeasurementData(co2, temperature, humidity); + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to read current temperature. Error code: %u", sensorName, error); + return false; + } + + LOG_INFO("%s: Current sensor temperature: %.2f", sensorName, temperature); + + tempOffset = (temperature - tempReference); + if (tempOffset < 0) { + LOG_ERROR("%s temperature offset is only positive", sensorName); + return false; + } + + tempOffset *= 100; + _tempOffset = static_cast(tempOffset); + } + + LOG_INFO("%s: Setting temperature offset: %u (*100)", sensorName, _tempOffset); + + error = scd30.setTemperatureOffset(_tempOffset); + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to set temperature offset. Error code: %u", sensorName, error); + return false; + } + + scd30.getTemperatureOffset(updatedTempOffset); + LOG_INFO("%s: Updated sensor temperature offset: %u (*100)", sensorName, updatedTempOffset); + + return true; +} + +bool SCD30Sensor::setAltitude(uint16_t altitude) +{ + uint16_t error; + + LOG_INFO("%s: setting altitude at %um", sensorName, altitude); + + error = scd30.setAltitudeCompensation(altitude); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to set altitude. Error code: %u", sensorName, error); + return false; + } + + uint16_t newAltitude; + getAltitude(newAltitude); + + return true; +} + +bool SCD30Sensor::getAltitude(uint16_t &altitude) +{ + uint16_t error; + // LOG_INFO("%s: Getting altitude", sensorName); + + error = scd30.getAltitudeCompensation(altitude); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to get altitude. Error code: %u", sensorName, error); + return false; + } + LOG_INFO("%s: Sensor altitude: %u", sensorName, altitude); + + return true; +} + +bool SCD30Sensor::softReset() +{ + uint16_t error; + + LOG_INFO("%s: Requesting soft reset", sensorName); + + error = scd30.softReset(); + + if (error != SCD30_NO_ERROR) { + LOG_ERROR("%s: Unable to do soft reset. Error code: %u", sensorName, error); + return false; + } + + LOG_INFO("%s: soft reset successful", sensorName); + + return true; +} + +/** + * @brief Check if sensor is in measurement mode + */ +bool SCD30Sensor::isActive() +{ + return state == SCD30_MEASUREMENT; +} + +/** + * @brief Start measurement mode + * @note Not used in admin comands, getMetrics or init, can change clock. + */ +uint32_t SCD30Sensor::wakeUp() +{ + +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return 0; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + startMeasurement(); + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + return 0; +} + +/** + * @brief Stop measurement mode + * @note Not used in admin comands, getMetrics or init, can change clock. + */ +void SCD30Sensor::sleep() +{ +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + stopMeasurement(); + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif +} + +bool SCD30Sensor::canSleep() +{ + return false; +} + +int32_t SCD30Sensor::wakeUpTimeMs() +{ + return 0; +} + +int32_t SCD30Sensor::pendingForReadyMs() +{ + return 0; +} + +AdminMessageHandleResult SCD30Sensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) +{ + AdminMessageHandleResult result; + +#ifdef SCD30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return AdminMessageHandleResult::NOT_HANDLED; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SCD30_I2C_CLOCK_SPEED */ + + switch (request->which_payload_variant) { + case meshtastic_AdminMessage_sensor_config_tag: + // Check for ASC-FRC request first + if (!request->sensor_config.has_scd30_config) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } + + if (request->sensor_config.scd30_config.has_soft_reset) { + LOG_DEBUG("%s: Requested soft reset", sensorName); + this->softReset(); + } else { + + if (request->sensor_config.scd30_config.has_set_asc) { + this->setASC(request->sensor_config.scd30_config.set_asc); + if (request->sensor_config.scd30_config.set_asc == false) { + LOG_DEBUG("%s: Request for FRC", sensorName); + if (request->sensor_config.scd30_config.has_set_target_co2_conc) { + this->performFRC(request->sensor_config.scd30_config.set_target_co2_conc); + } else { + // FRC requested but no target CO2 provided + LOG_ERROR("%s: target CO2 not provided", sensorName); + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } + } + } + + // Check for temperature offset + // NOTE: this requires to have a sensor working on stable environment + // And to make it between readings + if (request->sensor_config.scd30_config.has_set_temperature) { + this->setTemperature(request->sensor_config.scd30_config.set_temperature); + } + + // Check for altitude + if (request->sensor_config.scd30_config.has_set_altitude) { + this->setAltitude(request->sensor_config.scd30_config.set_altitude); + } + + // Check for set measuremen interval + if (request->sensor_config.scd30_config.has_set_measurement_interval) { + this->setMeasurementInterval(request->sensor_config.scd30_config.set_measurement_interval); + } + } + + result = AdminMessageHandleResult::HANDLED; + break; + + default: + result = AdminMessageHandleResult::NOT_HANDLED; + } + +#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + return result; +} + +#endif diff --git a/src/modules/Telemetry/Sensor/SCD30Sensor.h b/src/modules/Telemetry/Sensor/SCD30Sensor.h new file mode 100644 index 000000000..6e03e2dda --- /dev/null +++ b/src/modules/Telemetry/Sensor/SCD30Sensor.h @@ -0,0 +1,53 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +#define SCD30_I2C_CLOCK_SPEED 100000 + +class SCD30Sensor : public TelemetrySensor +{ + private: + SensirionI2cScd30 scd30; + TwoWire *_bus{}; + uint8_t _address{}; + + bool performFRC(uint16_t targetCO2); + bool setASC(bool ascEnabled); + bool getASC(uint16_t &ascEnabled); + bool setTemperature(float tempReference); + bool getAltitude(uint16_t &altitude); + bool setAltitude(uint16_t altitude); + bool softReset(); // + bool setMeasurementInterval(uint16_t measInterval); + bool getMeasurementInterval(uint16_t &measInterval); + bool startMeasurement(); + bool stopMeasurement(); + + // Parameters + uint16_t ascActive = 1; + uint16_t measurementInterval = 2; + + public: + SCD30Sensor(); + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + + enum SCD30State { SCD30_OFF, SCD30_IDLE, SCD30_MEASUREMENT }; + SCD30State state = SCD30_OFF; + + virtual bool isActive() override; + + virtual void sleep() override; // Stops measurement (measurement -> idle) + virtual uint32_t wakeUp() override; // Starts measurement (idle -> measurement) + virtual bool canSleep() override; + virtual int32_t wakeUpTimeMs() override; + virtual int32_t pendingForReadyMs() override; + AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, + meshtastic_AdminMessage *response) override; +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp index 4f6e28b4b..c6ab7bb04 100644 --- a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp @@ -111,7 +111,7 @@ bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement) bool dataReady; error = scd4x.getDataReadyStatus(dataReady); - if (!dataReady) { + if (error != SCD4X_NO_ERROR || !dataReady) { #if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) reClockI2C(currentClock, _bus, false); #endif @@ -125,7 +125,7 @@ bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement) reClockI2C(currentClock, _bus, false); #endif - LOG_DEBUG("%s readings: %u ppm, %.2f degC, %.2f %rh", sensorName, co2, temperature, humidity); + LOG_DEBUG("Got %s readings: co2=%u, co2_temp=%.2f, co2_hum%.2f", sensorName, co2, temperature, humidity); if (error != SCD4X_NO_ERROR) { LOG_DEBUG("%s: Error while getting measurements: %u", sensorName, error); if (co2 == 0) { @@ -217,7 +217,7 @@ bool SCD4XSensor::startMeasurement() state = SCD4X_MEASUREMENT; return true; } else { - LOG_ERROR("%s: Couldn't start measurement mode", sensorName); + LOG_ERROR("%s: Unable to start measurement mode", sensorName); return false; } } @@ -232,7 +232,7 @@ bool SCD4XSensor::stopMeasurement() error = scd4x.stopPeriodicMeasurement(); if (error != SCD4X_NO_ERROR) { - LOG_ERROR("%s: Unable to set idle mode on SCD4X.", sensorName); + LOG_ERROR("%s: Unable to stop measurement.", sensorName); return false; } @@ -283,11 +283,7 @@ bool SCD4XSensor::getASC(uint16_t &_ascActive) return false; } - if (_ascActive) { - LOG_INFO("%s: ASC is enabled", sensorName); - } else { - LOG_INFO("%s: FRC is enabled", sensorName); - } + LOG_INFO("%s ASC is %s", sensorName, _ascActive ? "enabled" : "disabled"); return true; } @@ -305,11 +301,7 @@ bool SCD4XSensor::setASC(bool ascEnabled) { uint16_t error; - if (ascEnabled) { - LOG_INFO("%s: Enabling ASC", sensorName); - } else { - LOG_INFO("%s: Disabling ASC", sensorName); - } + LOG_INFO("%s %s ASC", sensorName, ascEnabled ? "Enabling" : "Disabling"); if (!stopMeasurement()) { return false; @@ -333,12 +325,6 @@ bool SCD4XSensor::setASC(bool ascEnabled) return false; } - if (ascActive) { - LOG_INFO("%s: ASC is enabled", sensorName); - } else { - LOG_INFO("%s: ASC is disabled", sensorName); - } - return true; } @@ -357,8 +343,7 @@ bool SCD4XSensor::setASC(bool ascEnabled) */ bool SCD4XSensor::setASCBaseline(uint32_t targetCO2) { - // TODO - Remove? - // Available in library, but not described in datasheet. + // Available in library, but not described in datasheet. uint16_t error; LOG_INFO("%s: Setting ASC baseline to: %u", sensorName, targetCO2); @@ -425,7 +410,7 @@ bool SCD4XSensor::setTemperature(float tempReference) LOG_INFO("%s: Setting reference temperature at: %.2f", sensorName, tempReference); error = scd4x.getDataReadyStatus(dataReady); - if (!dataReady) { + if (error != SCD4X_NO_ERROR || !dataReady) { LOG_ERROR("%s: Data is not ready", sensorName); return false; } @@ -540,6 +525,7 @@ bool SCD4XSensor::setAltitude(uint32_t altitude) if (!stopMeasurement()) { return false; } + LOG_INFO("%s: setting altitude at %um", sensorName, altitude); error = scd4x.setSensorAltitude(altitude); @@ -548,11 +534,15 @@ bool SCD4XSensor::setAltitude(uint32_t altitude) return false; } - error = scd4x.persistSettings(); - if (error != SCD4X_NO_ERROR) { - LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error); - return false; - } + // NOTE: this gives an error if issued. Sensirion's library + // doesn't indicate it's needed. + // error = scd4x.persistSettings(); + // if (error != SCD4X_NO_ERROR) { + // LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error); + // return false; + // } + + LOG_INFO("%s: altitude set", sensorName); return true; } @@ -575,6 +565,8 @@ bool SCD4XSensor::setAmbientPressure(uint32_t ambientPressure) { uint16_t error; + LOG_INFO("%s: setting ambient pressure at %u Pa", sensorName, ambientPressure); + error = scd4x.setAmbientPressure(ambientPressure); if (error != SCD4X_NO_ERROR) { @@ -589,6 +581,8 @@ bool SCD4XSensor::setAmbientPressure(uint32_t ambientPressure) return false; } + LOG_INFO("%s: ambient pressure set set", sensorName); + return true; } @@ -824,15 +818,28 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa if (request->sensor_config.scd4x_config.has_factory_reset) { LOG_DEBUG("%s: Requested factory reset", sensorName); - this->factoryReset(); + if (!this->factoryReset()) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } else { - if (request->sensor_config.scd4x_config.has_set_asc) { - this->setASC(request->sensor_config.scd4x_config.set_asc); + getASC(ascActive); + bool currentASC = ascActive; if (request->sensor_config.scd4x_config.set_asc == false) { LOG_DEBUG("%s: Request for FRC", sensorName); if (request->sensor_config.scd4x_config.has_set_target_co2_conc) { - this->performFRC(request->sensor_config.scd4x_config.set_target_co2_conc); + if (this->setASC(request->sensor_config.scd4x_config.set_asc)) { + if (!this->performFRC(request->sensor_config.scd4x_config.set_target_co2_conc)) { + result = AdminMessageHandleResult::NOT_HANDLED; + // Set it back to ASC if failed + setASC(currentASC); + break; + }; + } else { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } else { // FRC requested but no target CO2 provided LOG_ERROR("%s: target CO2 not provided", sensorName); @@ -841,12 +848,17 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa } } else { LOG_DEBUG("%s: Request for ASC", sensorName); - if (request->sensor_config.scd4x_config.has_set_target_co2_conc) { - LOG_DEBUG("%s: Request has target CO2", sensorName); - // TODO - Remove? see setASCBaseline function - this->setASCBaseline(request->sensor_config.scd4x_config.set_target_co2_conc); + if (this->setASC(request->sensor_config.scd4x_config.set_asc)) { + if (request->sensor_config.scd4x_config.has_set_target_co2_conc) { + LOG_DEBUG("%s: Request has target CO2", sensorName); + this->setASCBaseline(request->sensor_config.scd4x_config.set_target_co2_conc); + // NOTE - in this situation, if we set ASC, but baseline set fails, we stay on ASC + } else { + LOG_DEBUG("%s: Request doesn't have target CO2", sensorName); + } } else { - LOG_DEBUG("%s: Request doesn't have target CO2", sensorName); + result = AdminMessageHandleResult::NOT_HANDLED; + break; } } } @@ -855,27 +867,36 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa // NOTE: this requires to have a sensor working on stable environment // And to make it between readings if (request->sensor_config.scd4x_config.has_set_temperature) { - this->setTemperature(request->sensor_config.scd4x_config.set_temperature); + if (!this->setTemperature(request->sensor_config.scd4x_config.set_temperature)) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } // Check for altitude or pressure offset if (request->sensor_config.scd4x_config.has_set_altitude) { - this->setAltitude(request->sensor_config.scd4x_config.set_altitude); + if (!this->setAltitude(request->sensor_config.scd4x_config.set_altitude)) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } else if (request->sensor_config.scd4x_config.has_set_ambient_pressure) { - this->setAmbientPressure(request->sensor_config.scd4x_config.set_ambient_pressure); + if (!this->setAmbientPressure(request->sensor_config.scd4x_config.set_ambient_pressure)) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } // Check for low power mode // NOTE: to switch from one mode to another do: // setPowerMode -> startMeasurement if (request->sensor_config.scd4x_config.has_set_power_mode) { - this->setPowerMode(request->sensor_config.scd4x_config.set_power_mode); + if (!this->setPowerMode(request->sensor_config.scd4x_config.set_power_mode)) { + result = AdminMessageHandleResult::NOT_HANDLED; + break; + } } } - // Start measurement mode - this->startMeasurement(); - result = AdminMessageHandleResult::HANDLED; break; @@ -883,6 +904,9 @@ AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPa result = AdminMessageHandleResult::NOT_HANDLED; } + // Start measurement mode + this->startMeasurement(); + #if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) reClockI2C(currentClock, _bus, false); #endif diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp index 2d890ca99..b0f0f9071 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -182,7 +182,7 @@ uint8_t SEN5XSensor::readBuffer(uint8_t *buffer, uint8_t byteNumber) return receivedBytes; } -uint8_t SEN5XSensor::sen5xCRC(uint8_t *buffer) +uint8_t SEN5XSensor::sen5xCRC(const uint8_t *buffer) { // This code is based on Sensirion's own implementation // https://github.com/Sensirion/arduino-core/blob/41fd02cacf307ec4945955c58ae495e56809b96c/src/SensirionCrc.cpp @@ -205,7 +205,6 @@ uint8_t SEN5XSensor::sen5xCRC(uint8_t *buffer) void SEN5XSensor::sleep() { - // TODO Check this works idle(true); } @@ -230,41 +229,43 @@ bool SEN5XSensor::idle(bool checkState) // Check if we have time, and store it uint32_t now; // If time is RTCQualityNone, it will return zero now = getValidTime(RTCQuality::RTCQualityDevice); + // Check if state is valid (non-zero) if (now) { - // Check if state is valid (non-zero) vocTime = now; } } - if (vocStateStable() && vocValid) { - saveState(); - } else { - LOG_INFO("SEN5X: Not stopping measurement, vocState is not stable yet!"); + if (!(vocStateStable() && vocValid)) { + LOG_INFO("%s: Not stopping measurement, vocState is not stable yet!", sensorName); return true; } } + // Save state and prefs (on all models) + saveState(); } if (!oneShotMode) { - LOG_INFO("SEN5X: Not stopping measurement, continuous mode!"); + LOG_INFO("%s: Not stopping measurement, continuous mode!", sensorName); return true; + } else { + LOG_INFO("%s: One shot mode enabled", sensorName); } // Switch to low-power based on the model if (model == SEN50) { if (!sendCommand(SEN5X_STOP_MEASUREMENT)) { - LOG_ERROR("SEN5X: Error stopping measurement"); + LOG_ERROR("%s: Error stopping measurement", sensorName); return false; } state = SEN5X_IDLE; - LOG_INFO("SEN5X: Stop measurement mode"); + LOG_INFO("%s: Stop measurement mode", sensorName); } else { if (!sendCommand(SEN5X_START_MEASUREMENT_RHT_GAS)) { - LOG_ERROR("SEN5X: Error switching to RHT/Gas measurement"); + LOG_ERROR("%s: Error switching to RHT/Gas measurement", sensorName); return false; } state = SEN5X_RHTGAS_ONLY; - LOG_INFO("SEN5X: Switch to RHT/Gas only measurement mode"); + LOG_INFO("%s: Switch to RHT/Gas only measurement mode", sensorName); } delay(200); // From Sensirion Datasheet @@ -289,10 +290,10 @@ bool SEN5XSensor::vocStateValid() { if (!vocState[0] && !vocState[1] && !vocState[2] && !vocState[3] && !vocState[4] && !vocState[5] && !vocState[6] && !vocState[7]) { - LOG_DEBUG("SEN5X: VOC state is all 0, invalid"); + LOG_DEBUG("%s: VOC state is all 0, invalid", sensorName); return false; } else { - LOG_DEBUG("SEN5X: VOC state is valid"); + LOG_DEBUG("%s: VOC state is valid", sensorName); return true; } } @@ -618,7 +619,7 @@ bool SEN5XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) } } else { // TODO - Should this actually ignore? We could end up never cleaning... - LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved state. Trying again later"); + LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved cleaning and VOC state"); } idle(false); @@ -665,16 +666,16 @@ bool SEN5XSensor::readValues() sen5xmeasurement.vocIndex = !isnan(int_vocIndex) ? int_vocIndex / 10.0f : FLT_MAX; sen5xmeasurement.noxIndex = !isnan(int_noxIndex) ? int_noxIndex / 10.0f : FLT_MAX; - LOG_DEBUG("Got: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sen5xmeasurement.pM1p0, sen5xmeasurement.pM2p5, - sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0); + LOG_DEBUG("Got %s readings: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sensorName, sen5xmeasurement.pM1p0, + sen5xmeasurement.pM2p5, sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0); if (model != SEN50) { - LOG_DEBUG("Got: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sen5xmeasurement.humidity, sen5xmeasurement.temperature, - sen5xmeasurement.vocIndex); + LOG_DEBUG("Got %s readings: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sensorName, sen5xmeasurement.humidity, + sen5xmeasurement.temperature, sen5xmeasurement.vocIndex); } if (model == SEN55) { - LOG_DEBUG("Got: noxIndex=%.2f", sen5xmeasurement.noxIndex); + LOG_DEBUG("Got %s readings: noxIndex=%.2f", sensorName, sen5xmeasurement.noxIndex); } return true; @@ -727,9 +728,9 @@ bool SEN5XSensor::readPNValues(bool cumulative) sen5xmeasurement.pN1p0 -= sen5xmeasurement.pN0p5; } - LOG_DEBUG("Got: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f", sen5xmeasurement.pN0p5, - sen5xmeasurement.pN1p0, sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0, sen5xmeasurement.pN10p0, - sen5xmeasurement.tSize); + LOG_DEBUG("Got %s readings: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f", sensorName, + sen5xmeasurement.pN0p5, sen5xmeasurement.pN1p0, sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0, + sen5xmeasurement.pN10p0, sen5xmeasurement.tSize); return true; } @@ -919,6 +920,11 @@ bool SEN5XSensor::getMetrics(meshtastic_Telemetry *measurement) void SEN5XSensor::setMode(bool setOneShot) { oneShotMode = setOneShot; + if (oneShotMode) { + LOG_INFO("%s setting mode to one shot mode", sensorName); + } else { + LOG_INFO("%s setting mode to continuous mode", sensorName); + } } AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request, @@ -934,16 +940,22 @@ AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPa break; } - // TODO - Add admin command to set temperature offset + // Check for one-shot/continuous mode request + if (request->sensor_config.sen5x_config.has_set_one_shot_mode) { + this->setMode(request->sensor_config.sen5x_config.set_one_shot_mode); + } + + // TODO - Add admin command to set temperature offset? // Check for temperature offset // if (request->sensor_config.sen5x_config.has_set_temperature) { // this->setTemperature(request->sensor_config.sen5x_config.set_temperature); // } + // TODO - Add admin command to trigger fan cleaning? // Check for one-shot/continuous mode request - if (request->sensor_config.sen5x_config.has_set_one_shot_mode) { - this->setMode(request->sensor_config.sen5x_config.set_one_shot_mode); - } + // if (request->sensor_config.sen5x_config.has_fan_cleaning && request->sensor_config.sen5x_config.fan_cleaning) { + // this->startCleaning(); + // } result = AdminMessageHandleResult::HANDLED; break; @@ -954,4 +966,4 @@ AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPa return result; } -#endif \ No newline at end of file +#endif diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.h b/src/modules/Telemetry/Sensor/SEN5XSensor.h index 46f8c70e9..ef5ad5c29 100644 --- a/src/modules/Telemetry/Sensor/SEN5XSensor.h +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.h @@ -114,7 +114,7 @@ See: https://sensirion.com/resource/application_note/low_power_mode/sen5x bool sendCommand(uint16_t command); bool sendCommand(uint16_t command, uint8_t *buffer, uint8_t byteNumber = 0); uint8_t readBuffer(uint8_t *buffer, uint8_t byteNumber); // Return number of bytes received - uint8_t sen5xCRC(uint8_t *buffer); + uint8_t sen5xCRC(const uint8_t *buffer); bool startCleaning(); uint8_t getMeasurements(); // bool readRawValues(); diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp new file mode 100644 index 000000000..c5b5845d9 --- /dev/null +++ b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp @@ -0,0 +1,198 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../detect/reClockI2C.h" +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SFA30Sensor.h" + +SFA30Sensor::SFA30Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SFA30, "SFA30"){}; + +bool SFA30Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) +{ + LOG_INFO("Init sensor: %s", sensorName); + + _bus = bus; + _address = dev->address.address; + +#ifdef SFA30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SFA30_I2C_CLOCK_SPEED */ + + sfa30.begin(*_bus, _address); + delay(20); + + if (this->isError(sfa30.deviceReset())) { +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + state = State::IDLE; + if (this->isError(sfa30.startContinuousMeasurement())) { +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return false; + } + + LOG_INFO("%s starting measurement", sensorName); + +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + status = 1; + state = State::ACTIVE; + measureStarted = getTime(); + LOG_INFO("%s Enabled", sensorName); + + initI2CSensor(); + return true; +}; + +bool SFA30Sensor::isError(uint16_t response) +{ + if (response == SFA30_NO_ERROR) + return false; + + // TODO - Check error to char conversion + LOG_ERROR("%s: %u", sensorName, response); + return true; +} + +void SFA30Sensor::sleep() +{ +#ifdef SFA30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SFA30_I2C_CLOCK_SPEED */ + + // Note - not recommended for this sensor on a periodic basis + if (this->isError(sfa30.stopMeasurement())) { + LOG_ERROR("%s: can't stop measurement", sensorName); + }; + +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + LOG_INFO("%s: stop measurement", sensorName); + state = State::IDLE; + measureStarted = 0; +} + +uint32_t SFA30Sensor::wakeUp() +{ +#ifdef SFA30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SFA30_I2C_CLOCK_SPEED */ + + LOG_INFO("Waking up %s", sensorName); + if (this->isError(sfa30.startContinuousMeasurement())) { +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + return 0; + } + +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + state = State::ACTIVE; + measureStarted = getTime(); + return SFA30_WARMUP_MS; +} + +int32_t SFA30Sensor::wakeUpTimeMs() +{ + return SFA30_WARMUP_MS; +} + +bool SFA30Sensor::canSleep() +{ + // Sleep is disabled in this sensor because readings are not tested with periodic sleep + // with such low power consumption, prefered to keep it active + return false; +} + +bool SFA30Sensor::isActive() +{ + return state == State::ACTIVE; +} + +int32_t SFA30Sensor::pendingForReadyMs() +{ + uint32_t now; + now = getTime(); + uint32_t sinceHchoMeasureStarted = (now - measureStarted) * 1000; + LOG_DEBUG("%s: Since measure started: %ums", sensorName, sinceHchoMeasureStarted); + + if (sinceHchoMeasureStarted < SFA30_WARMUP_MS) { + LOG_INFO("%s: not enough time passed since starting measurement", sensorName); + return SFA30_WARMUP_MS - sinceHchoMeasureStarted; + } + return 0; +} + +bool SFA30Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + float hcho = 0.0; + float humidity = 0.0; + float temperature = 0.0; + +#ifdef SFA30_I2C_CLOCK_SPEED +#ifdef CAN_RECLOCK_I2C + uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false); +#elif !HAS_SCREEN + reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, true); +#else + LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName); + return false; +#endif /* CAN_RECLOCK_I2C */ +#endif /* SFA30_I2C_CLOCK_SPEED */ + + if (this->isError(sfa30.readMeasuredValues(hcho, humidity, temperature))) { + LOG_WARN("%s: No values", sensorName); + return false; + } + +#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C) + reClockI2C(currentClock, _bus, false); +#endif + + measurement->variant.air_quality_metrics.has_form_temperature = true; + measurement->variant.air_quality_metrics.has_form_humidity = true; + measurement->variant.air_quality_metrics.has_form_formaldehyde = true; + + measurement->variant.air_quality_metrics.form_temperature = temperature; + measurement->variant.air_quality_metrics.form_humidity = humidity; + measurement->variant.air_quality_metrics.form_formaldehyde = hcho; + + LOG_DEBUG("Got %s readings: hcho=%.2f, hcho_temp=%.2f, hcho_hum=%.2f", sensorName, hcho, temperature, humidity); + + return true; +} +#endif diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.h b/src/modules/Telemetry/Sensor/SFA30Sensor.h new file mode 100644 index 000000000..9fa9c85fc --- /dev/null +++ b/src/modules/Telemetry/Sensor/SFA30Sensor.h @@ -0,0 +1,39 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "RTC.h" +#include "TelemetrySensor.h" +#include + +#define SFA30_I2C_CLOCK_SPEED 100000 +#define SFA30_WARMUP_MS 10000 +#define SFA30_NO_ERROR 0 + +class SFA30Sensor : public TelemetrySensor +{ + private: + enum class State { IDLE, ACTIVE }; + State state = State::IDLE; + uint32_t measureStarted = 0; + + SensirionI2cSfa3x sfa30; + TwoWire *_bus{}; + uint8_t _address{}; + bool isError(uint16_t response); + + public: + SFA30Sensor(); + virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + + virtual bool isActive() override; + virtual void sleep() override; + virtual uint32_t wakeUp() override; + virtual bool canSleep() override; + virtual int32_t wakeUpTimeMs() override; + virtual int32_t pendingForReadyMs() override; +}; + +#endif diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index 41dc02cd1..3371c405d 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -266,7 +266,7 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti } } -void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) +void TraceRouteModule::updateNextHops(const meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) { // E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D // Similarly, if we are C, we can set D as next-hop for D diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index a40ed7733..db94b9d9b 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -62,7 +62,7 @@ class TraceRouteModule : public ProtobufModule, void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly); // Update next-hops in the routing table based on the returned route - void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r); + void updateNextHops(const meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r); // Helper to update next-hop for a single node void maybeSetNextHop(NodeNum target, uint8_t nextHopByte); diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 18a4f913e..2c10c4b2b 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -754,10 +754,8 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) { // For uplinking other's packets, check if it's not OK to MQTT or if it's an older packet without the bitfield bool dontUplink = !mp_decoded.decoded.has_bitfield || !(mp_decoded.decoded.bitfield & BITFIELD_OK_TO_MQTT_MASK); - // check for the lowest bit of the data bitfield set false, and the use of one of the default keys. - if (!isFromUs(&mp_decoded) && !isMqttServerAddressPrivate && dontUplink && - (ch.settings.psk.size < 2 || (ch.settings.psk.size == 16 && memcmp(ch.settings.psk.bytes, defaultpsk, 16)) || - (ch.settings.psk.size == 32 && memcmp(ch.settings.psk.bytes, eventpsk, 32)))) { + // Respect the DontMqttMeBro flag for other nodes' packets on public MQTT servers + if (!isFromUs(&mp_decoded) && !isMqttServerAddressPrivate && dontUplink) { LOG_INFO("MQTT onSend - Not forwarding packet due to DontMqttMeBro flag"); return; } diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 2a59c0aab..89d74dbd7 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -757,11 +757,7 @@ void NimbleBluetooth::deinit() isDeInit = true; #ifdef BLE_LED -#ifdef BLE_LED_INVERTED - digitalWrite(BLE_LED, HIGH); -#else - digitalWrite(BLE_LED, LOW); -#endif + digitalWrite(BLE_LED, LED_STATE_OFF); #endif #ifndef NIMBLE_TWO NimBLEDevice::deinit(); diff --git a/src/platform/esp32/MeshtasticOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp index 4ca074723..20a3c59cc 100644 --- a/src/platform/esp32/MeshtasticOTA.cpp +++ b/src/platform/esp32/MeshtasticOTA.cpp @@ -75,7 +75,7 @@ bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc) return true; } -bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method) +bool checkOTACapability(const esp_app_desc_t *app_desc, uint8_t method) { // Combined loader supports all (both) transports, BLE and WiFi if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) { diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h index 7c158775f..ce5a7e86b 100644 --- a/src/platform/esp32/MeshtasticOTA.h +++ b/src/platform/esp32/MeshtasticOTA.h @@ -16,7 +16,7 @@ void initialize(); bool isUpdated(); const esp_partition_t *getAppPartition(); bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc); -bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method); +bool checkOTACapability(const esp_app_desc_t *app_desc, uint8_t method); void recoverConfig(meshtastic_Config_NetworkConfig *network); void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash); bool trySwitchToOTA(); diff --git a/src/platform/nrf52/AsyncUDP.h b/src/platform/nrf52/AsyncUDP.h index 718277309..ecbcb12fb 100644 --- a/src/platform/nrf52/AsyncUDP.h +++ b/src/platform/nrf52/AsyncUDP.h @@ -38,7 +38,7 @@ class AsyncUDP : public Print, private concurrency::OSThread class AsyncUDPPacket { public: - AsyncUDPPacket(EthernetUDP &source); + explicit AsyncUDPPacket(EthernetUDP &source); IPAddress remoteIP(); uint16_t length(); diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index d1965f03e..f73a5b896 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -158,7 +158,7 @@ #endif #ifdef PIN_LED1 -#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK +#define LED_POWER PIN_LED1 // LED1 on nrf52840-DK #endif #ifdef PIN_BUTTON1 diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index d0d8ba40f..3f0b2147c 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -496,7 +496,7 @@ void portduinoSetup() randomSeed(time(NULL)); std::string defaultGpioChipName = gpioChipName + std::to_string(portduino_config.lora_default_gpiochip); - for (auto i : portduino_config.all_pins) { + for (const auto *i : portduino_config.all_pins) { if (i->enabled && i->pin > max_GPIO) max_GPIO = i->pin; } @@ -510,7 +510,7 @@ void portduinoSetup() // Need to bind all the configured GPIO pins so they're not simulated // TODO: If one of these fails, we should log and terminate - for (auto i : portduino_config.all_pins) { + for (const auto *i : portduino_config.all_pins) { // In the case of a ch341 Lora device, we don't want to touch the system GPIO lines for Lora // Those GPIO are handled in our usermode driver instead. if (i->config_section == "Lora" && portduino_config.lora_spi_dev == "ch341") { @@ -572,7 +572,7 @@ void portduinoSetup() return; } -int initGPIOPin(int pinNum, const std::string gpioChipName, int line) +int initGPIOPin(int pinNum, const std::string &gpioChipName, int line) { #ifdef PORTDUINO_LINUX_HARDWARE std::string gpio_name = "GPIO" + std::to_string(pinNum); @@ -643,7 +643,7 @@ bool loadConfig(const char *configPath) if (yamlConfig["Lora"]) { if (yamlConfig["Lora"]["Module"]) { - for (auto &loraModule : portduino_config.loraModules) { + for (const auto &loraModule : portduino_config.loraModules) { if (yamlConfig["Lora"]["Module"].as("") == loraModule.second) { portduino_config.lora_module = loraModule.first; break; @@ -777,7 +777,7 @@ bool loadConfig(const char *configPath) } if (yamlConfig["Display"]) { - for (auto &screen_name : portduino_config.screen_names) { + for (const auto &screen_name : portduino_config.screen_names) { if (yamlConfig["Display"]["Panel"].as("") == screen_name.second) portduino_config.displayPanel = screen_name.first; } @@ -872,6 +872,7 @@ bool loadConfig(const char *configPath) } if (yamlConfig["Config"]) { + portduino_config.has_config_overrides = true; if (yamlConfig["Config"]["DisplayMode"]) { portduino_config.has_configDisplayMode = true; if ((yamlConfig["Config"]["DisplayMode"]).as("") == "TWOCOLOR") { @@ -884,6 +885,13 @@ bool loadConfig(const char *configPath) portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; } } + if (yamlConfig["Config"]["StatusMessage"]) { + portduino_config.has_statusMessage = true; + portduino_config.statusMessage = (yamlConfig["Config"]["StatusMessage"]).as(""); + } + if ((yamlConfig["Config"]["EnableUDP"]).as(false)) { + portduino_config.enable_UDP = true; + } } if (yamlConfig["General"]) { @@ -899,7 +907,7 @@ bool loadConfig(const char *configPath) } if (checkConfigPort) { portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as(-1); - if (portduino_config.api_port != -1 && portduino_config.api_port > 1023 && portduino_config.api_port < 65536) { + if (portduino_config.api_port > 1023 && portduino_config.api_port < 65536) { TCPPort = (portduino_config.api_port); } } diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index af511be6e..aa8847fd7 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -58,7 +58,7 @@ extern std::ofstream traceFile; extern std::ofstream JSONFile; extern Ch341Hal *ch341Hal; -int initGPIOPin(int pinNum, std::string gpioChipname, int line); +int initGPIOPin(int pinNum, const std::string &gpioChipname, int line); bool loadConfig(const char *configPath); static bool ends_with(std::string_view str, std::string_view suffix); void getMacAddr(uint8_t *dmac); @@ -177,8 +177,12 @@ extern struct portduino_config_struct { int hostMetrics_channel = 0; // config + bool has_config_overrides = false; int configDisplayMode = 0; bool has_configDisplayMode = false; + std::string statusMessage = ""; + bool has_statusMessage = false; + bool enable_UDP = false; // General std::string mac_address = ""; @@ -220,7 +224,7 @@ extern struct portduino_config_struct { out << YAML::Key << "Lora" << YAML::Value << YAML::BeginMap; out << YAML::Key << "Module" << YAML::Value << loraModules[lora_module]; - for (auto lora_pin : all_pins) { + for (const auto *lora_pin : all_pins) { if (lora_pin->config_section == "Lora" && lora_pin->enabled) { out << YAML::Key << lora_pin->config_name << YAML::Value << YAML::BeginMap; out << YAML::Key << "pin" << YAML::Value << lora_pin->pin; @@ -346,11 +350,11 @@ extern struct portduino_config_struct { // Display if (displayPanel != no_screen) { out << YAML::Key << "Display" << YAML::Value << YAML::BeginMap; - for (auto &screen_name : screen_names) { + for (const auto &screen_name : screen_names) { if (displayPanel == screen_name.first) out << YAML::Key << "Module" << YAML::Value << screen_name.second; } - for (auto display_pin : all_pins) { + for (const auto *display_pin : all_pins) { if (display_pin->config_section == "Display" && display_pin->enabled) { out << YAML::Key << display_pin->config_name << YAML::Value << YAML::BeginMap; out << YAML::Key << "pin" << YAML::Value << display_pin->pin; @@ -398,7 +402,7 @@ extern struct portduino_config_struct { case ft5x06: out << YAML::Key << "Module" << YAML::Value << "FT5x06"; } - for (auto touchscreen_pin : all_pins) { + for (const auto *touchscreen_pin : all_pins) { if (touchscreen_pin->config_section == "Touchscreen" && touchscreen_pin->enabled) { out << YAML::Key << touchscreen_pin->config_name << YAML::Value << YAML::BeginMap; out << YAML::Key << "pin" << YAML::Value << touchscreen_pin->pin; @@ -421,7 +425,7 @@ extern struct portduino_config_struct { if (pointerDevice != "") out << YAML::Key << "PointerDevice" << YAML::Value << pointerDevice; - for (auto input_pin : all_pins) { + for (const auto *input_pin : all_pins) { if (input_pin->config_section == "Input" && input_pin->enabled) { out << YAML::Key << input_pin->config_name << YAML::Value << YAML::BeginMap; out << YAML::Key << "pin" << YAML::Value << input_pin->pin; @@ -505,21 +509,30 @@ extern struct portduino_config_struct { } // config - if (has_configDisplayMode) { + if (has_config_overrides) { out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap; - switch (configDisplayMode) { - case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: - out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: - out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: - out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; - break; - case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: - out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; - break; + if (has_configDisplayMode) { + + switch (configDisplayMode) { + case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: + out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; + break; + case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: + out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; + break; + } + } + if (has_statusMessage) { + out << YAML::Key << "StatusMessage" << YAML::Value << statusMessage; + } + if (enable_UDP) { + out << YAML::Key << "EnableUDP" << YAML::Value << true; } out << YAML::EndMap; // Config diff --git a/src/platform/portduino/USBHal.h b/src/platform/portduino/USBHal.h index 441f75b10..1725763f2 100644 --- a/src/platform/portduino/USBHal.h +++ b/src/platform/portduino/USBHal.h @@ -29,7 +29,7 @@ class Ch341Hal : public RadioLibHal { public: // default constructor - initializes the base HAL and any needed private members - explicit Ch341Hal(uint8_t spiChannel, std::string serial = "", uint32_t vid = 0x1A86, uint32_t pid = 0x5512, + explicit Ch341Hal(uint8_t spiChannel, const std::string &serial = "", uint32_t vid = 0x1A86, uint32_t pid = 0x5512, uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0) : RadioLibHal(PI_INPUT, PI_OUTPUT, PI_LOW, PI_HIGH, PI_RISING, PI_FALLING) { diff --git a/src/power.h b/src/power.h index e4b456d3b..b129e2b74 100644 --- a/src/power.h +++ b/src/power.h @@ -103,8 +103,10 @@ class Power : private concurrency::OSThread bool axpChipInit(); /// Setup a simple ADC input based battery sensor bool analogInit(); - /// Setup a Lipo battery level sensor - bool lipoInit(); + /// Setup cw2015 battery level sensor + bool cw2015Init(); + /// Setup a 17048 battery level sensor + bool max17048Init(); /// Setup a Lipo charger bool lipoChargerInit(); /// Setup a meshSolar battery sensor diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index 042bc3763..819ba3da5 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -158,6 +158,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.air_quality_metrics.has_co2_humidity) { msgPayload["co2_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.co2_humidity); } + if (decoded->variant.air_quality_metrics.has_form_formaldehyde) { + msgPayload["form_formaldehyde"] = new JSONValue(decoded->variant.air_quality_metrics.form_formaldehyde); + } + if (decoded->variant.air_quality_metrics.has_form_temperature) { + msgPayload["form_temperature"] = new JSONValue(decoded->variant.air_quality_metrics.form_temperature); + } + if (decoded->variant.air_quality_metrics.has_form_humidity) { + msgPayload["form_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.form_humidity); + } } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { if (decoded->variant.power_metrics.has_ch1_voltage) { msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage); diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index a0ad4e4b9..bd0a29c51 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -129,6 +129,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, if (decoded->variant.air_quality_metrics.has_co2_humidity) { jsonObj["payload"]["co2_humidity"] = decoded->variant.air_quality_metrics.co2_humidity; } + if (decoded->variant.air_quality_metrics.has_form_formaldehyde) { + jsonObj["payload"]["form_formaldehyde"] = decoded->variant.air_quality_metrics.form_formaldehyde; + } + if (decoded->variant.air_quality_metrics.has_form_temperature) { + jsonObj["payload"]["form_temperature"] = decoded->variant.air_quality_metrics.form_temperature; + } + if (decoded->variant.air_quality_metrics.has_form_humidity) { + jsonObj["payload"]["form_humidity"] = decoded->variant.air_quality_metrics.form_humidity; + } } else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) { if (decoded->variant.power_metrics.has_ch1_voltage) { jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage; diff --git a/src/sleep.cpp b/src/sleep.cpp index 8b30a5352..7c768d573 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -5,7 +5,6 @@ #endif #include "Default.h" -#include "Led.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -13,6 +12,7 @@ #include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" +#include "modules/StatusLEDModule.h" #include "sleep.h" #include "target_specific.h" @@ -268,8 +268,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN digitalWrite(PIN_WD_EN, LOW); #endif #endif - ledBlink.set(false); - + statusLEDModule->setPowerLED(false); #ifdef RESET_OLED digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power #endif diff --git a/variants/esp32/betafpv_2400_tx_micro/variant.h b/variants/esp32/betafpv_2400_tx_micro/variant.h index 67699e7c8..8e875a3e6 100644 --- a/variants/esp32/betafpv_2400_tx_micro/variant.h +++ b/variants/esp32/betafpv_2400_tx_micro/variant.h @@ -14,7 +14,7 @@ #define LORA_CS 5 #define RF95_FAN_EN 17 -// #define LED_PIN 16 // This is a LED_WS2812 not a standard LED +// This is a LED_WS2812 not a standard LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected #define NEOPIXEL_DATA 16 // gpio pin used to send data to the neopixels diff --git a/variants/esp32/betafpv_900_tx_nano/variant.h b/variants/esp32/betafpv_900_tx_nano/variant.h index 7a4ae9190..6bee74d90 100644 --- a/variants/esp32/betafpv_900_tx_nano/variant.h +++ b/variants/esp32/betafpv_900_tx_nano/variant.h @@ -20,7 +20,7 @@ #define LORA_DIO2 #define LORA_DIO3 -#define LED_PIN 16 // green - blue is at 17 +#define LED_POWER 16 // green - blue is at 17 #define BUTTON_PIN 25 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32/chatter2/variant.h b/variants/esp32/chatter2/variant.h index 28ce64f91..e91e4dcef 100644 --- a/variants/esp32/chatter2/variant.h +++ b/variants/esp32/chatter2/variant.h @@ -23,8 +23,6 @@ #define SX126X_TXEN RADIOLIB_NC #define SX126X_RXEN RADIOLIB_NC -// Status -// #define LED_PIN 1 // External notification // FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the // app/preferences diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini index 2ddc5a2db..3fdb738fc 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini @@ -10,6 +10,7 @@ build_flags = -D EBYTE_E22 -D EBYTE_E22_900M30S ; Assume Tx power curve is identical to 900M30S as there is no documentation -I variants/esp32/diy/9m2ibr_aprs_lora_tracker + -ULED_BUILTIN build_src_filter = ${esp32_base.build_src_filter} +<../variants/esp32/diy/9m2ibr_aprs_lora_tracker> \ No newline at end of file diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h index 037933140..1f84fffa1 100644 --- a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h @@ -21,8 +21,8 @@ #define BUTTON_PIN 15 // Right side button - if not available, set device.button_gpio to 0 from Meshtastic client // LEDs -#define LED_PIN 13 // Tx LED -#define USER_LED 2 // Rx LED +#define LED_POWER 13 // Tx LED +#define USER_LED 2 // Rx LED // Buzzer #define PIN_BUZZER 33 diff --git a/variants/esp32/diy/hydra/variant.h b/variants/esp32/diy/hydra/variant.h index e5c10e26b..68194f869 100644 --- a/variants/esp32/diy/hydra/variant.h +++ b/variants/esp32/diy/hydra/variant.h @@ -15,7 +15,7 @@ #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) #define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). -#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets) +#define LED_POWER 2 // add status LED (compatible with core-pcb and DIY targets) // Radio #define USE_SX1262 // E22-900M30S uses SX1262 diff --git a/variants/esp32/diy/v1/variant.h b/variants/esp32/diy/v1/variant.h index 8a2df3f2b..862969af0 100644 --- a/variants/esp32/diy/v1/variant.h +++ b/variants/esp32/diy/v1/variant.h @@ -15,7 +15,7 @@ #define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k) #define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards #define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975). -#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets) +#define LED_POWER 2 // add status LED (compatible with core-pcb and DIY targets) #define LORA_DIO0 26 // a No connect on the SX1262/SX1268 module #define LORA_RESET 23 // RST for SX1276, and for SX1262/SX1268 diff --git a/variants/esp32/hackerboxes_esp32_io/variant.h b/variants/esp32/hackerboxes_esp32_io/variant.h index 06f0032ee..42ce2423e 100644 --- a/variants/esp32/hackerboxes_esp32_io/variant.h +++ b/variants/esp32/hackerboxes_esp32_io/variant.h @@ -3,7 +3,7 @@ // HACKBOX LoRa IO Kit // Uses a ESP-32-WROOM and a RA-01SH (SX1262) LoRa Board -#define LED_PIN 2 // LED +#define LED_POWER 2 // LED #define LED_STATE_ON 1 // State when LED is lit #define HAS_SCREEN 0 diff --git a/variants/esp32/heltec_v1/variant.h b/variants/esp32/heltec_v1/variant.h index d1338a28e..c4f6577a8 100644 --- a/variants/esp32/heltec_v1/variant.h +++ b/variants/esp32/heltec_v1/variant.h @@ -12,7 +12,7 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define USE_RF95 diff --git a/variants/esp32/heltec_v2.1/platformio.ini b/variants/esp32/heltec_v2.1/platformio.ini index 1f7caa16f..9fcb2388a 100644 --- a/variants/esp32/heltec_v2.1/platformio.ini +++ b/variants/esp32/heltec_v2.1/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D HELTEC_V2_1 -I variants/esp32/heltec_v2.1 + -ULED_BUILTIN diff --git a/variants/esp32/heltec_v2.1/variant.h b/variants/esp32/heltec_v2.1/variant.h index 8ebccc54f..e4aeb363d 100644 --- a/variants/esp32/heltec_v2.1/variant.h +++ b/variants/esp32/heltec_v2.1/variant.h @@ -18,7 +18,7 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define USE_RF95 diff --git a/variants/esp32/heltec_v2/platformio.ini b/variants/esp32/heltec_v2/platformio.ini index 5f15fb321..fc9e05115 100644 --- a/variants/esp32/heltec_v2/platformio.ini +++ b/variants/esp32/heltec_v2/platformio.ini @@ -14,3 +14,4 @@ build_flags = ${esp32_base.build_flags} -D HELTEC_V2_0 -I variants/esp32/heltec_v2 + -ULED_BUILTIN diff --git a/variants/esp32/heltec_v2/variant.h b/variants/esp32/heltec_v2/variant.h index 5c183818b..c35465f81 100644 --- a/variants/esp32/heltec_v2/variant.h +++ b/variants/esp32/heltec_v2/variant.h @@ -13,7 +13,7 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define USE_RF95 diff --git a/variants/esp32/heltec_wireless_bridge/variant.h b/variants/esp32/heltec_wireless_bridge/variant.h index 5ad16d0e2..82cc83aa8 100644 --- a/variants/esp32/heltec_wireless_bridge/variant.h +++ b/variants/esp32/heltec_wireless_bridge/variant.h @@ -15,7 +15,7 @@ #undef GPS_TX_PIN // Green / Lora = PIN 22 / GPIO2, Yellow / Wifi = PIN 23 / GPIO0, Blue / BLE = PIN 25 / GPIO16 -#define LED_PIN 22 +#define LED_POWER 22 #define WIFI_LED 23 #define BLE_LED 25 diff --git a/variants/esp32/heltec_wsl_v2.1/variant.h b/variants/esp32/heltec_wsl_v2.1/variant.h index 3927a89d6..db374afb6 100644 --- a/variants/esp32/heltec_wsl_v2.1/variant.h +++ b/variants/esp32/heltec_wsl_v2.1/variant.h @@ -1,7 +1,7 @@ #define I2C_SCL SCL #define I2C_SDA SDA -#define LED_PIN LED +#define LED_POWER LED // active low, powers the Battery reader, but no lora antenna boost (?) // #define VEXT_ENABLE Vext diff --git a/variants/esp32/m5stack_coreink/variant.h b/variants/esp32/m5stack_coreink/variant.h index 9bf45f2ff..84a1e1966 100644 --- a/variants/esp32/m5stack_coreink/variant.h +++ b/variants/esp32/m5stack_coreink/variant.h @@ -11,7 +11,7 @@ // Green LED #define LED_STATE_ON 1 // State when LED is lit -#define LED_PIN 10 +#define LED_POWER 10 // PCF8563 RTC Module #define PCF8563_RTC 0x51 diff --git a/variants/esp32/radiomaster_900_bandit_nano/variant.h b/variants/esp32/radiomaster_900_bandit_nano/variant.h index 1b6bba126..318401f92 100644 --- a/variants/esp32/radiomaster_900_bandit_nano/variant.h +++ b/variants/esp32/radiomaster_900_bandit_nano/variant.h @@ -37,7 +37,7 @@ /* LED PIN setup. */ -#define LED_PIN 15 +#define LED_POWER 15 /* Five way button when using ADC. diff --git a/variants/esp32/rak11200/variant.h b/variants/esp32/rak11200/variant.h index fe7d05676..a38ac83b7 100644 --- a/variants/esp32/rak11200/variant.h +++ b/variants/esp32/rak11200/variant.h @@ -43,7 +43,7 @@ static const uint8_t SCK = 33; #undef GPS_TX_PIN #define GPS_TX_PIN (TX1) -#define LED_PIN LED_BLUE +#define LED_POWER LED_BLUE #define PIN_VBAT WB_A0 #define BATTERY_PIN PIN_VBAT diff --git a/variants/esp32/tbeam/variant.h b/variants/esp32/tbeam/variant.h index 2c1e61c49..cca52cb9a 100644 --- a/variants/esp32/tbeam/variant.h +++ b/variants/esp32/tbeam/variant.h @@ -9,7 +9,7 @@ #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. #define LED_STATE_ON 0 // State when LED is lit -#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4 +#define LED_POWER 4 // Newer tbeams (1.1) have an extra led on GPIO4 // TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if // not found then probe for SX1262 @@ -49,7 +49,7 @@ #undef EXT_NOTIFY_OUT #undef LED_STATE_ON -#undef LED_PIN +#undef LED_POWER #define HAS_CST226SE 1 #define HAS_TOUCHSCREEN 1 diff --git a/variants/esp32/tlora_v1/variant.h b/variants/esp32/tlora_v1/variant.h index 83e2c193e..ff9f4a8ef 100644 --- a/variants/esp32/tlora_v1/variant.h +++ b/variants/esp32/tlora_v1/variant.h @@ -5,7 +5,7 @@ #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost #define VEXT_ON_VALUE LOW -#define LED_PIN 2 // If defined we will blink this LED +#define LED_POWER 2 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define BUTTON_NEED_PULLUP #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module. diff --git a/variants/esp32/tlora_v1_3/variant.h b/variants/esp32/tlora_v1_3/variant.h index 73cb31f27..2b0395d8a 100644 --- a/variants/esp32/tlora_v1_3/variant.h +++ b/variants/esp32/tlora_v1_3/variant.h @@ -7,7 +7,7 @@ #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 36 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32/tlora_v2/variant.h b/variants/esp32/tlora_v2/variant.h index 8a7cf89ec..099fdc2ee 100644 --- a/variants/esp32/tlora_v2/variant.h +++ b/variants/esp32/tlora_v2/variant.h @@ -5,7 +5,7 @@ #define I2C_SCL 22 #define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN \ 0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one // between this pin and ground diff --git a/variants/esp32/tlora_v2_1_16/platformio.ini b/variants/esp32/tlora_v2_1_16/platformio.ini index dfdbcb152..2ea9bbb50 100644 --- a/variants/esp32/tlora_v2_1_16/platformio.ini +++ b/variants/esp32/tlora_v2_1_16/platformio.ini @@ -22,4 +22,4 @@ build_flags = ${env:tlora-v2-1-1_6.build_flags} -DBUTTON_PIN=0 -DPIN_BUZZER=25 - -DLED_PIN=-1 \ No newline at end of file + -DLED_POWER=-1 \ No newline at end of file diff --git a/variants/esp32/tlora_v2_1_16/variant.h b/variants/esp32/tlora_v2_1_16/variant.h index 9584dd68b..5488fddf4 100644 --- a/variants/esp32/tlora_v2_1_16/variant.h +++ b/variants/esp32/tlora_v2_1_16/variant.h @@ -8,10 +8,10 @@ #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 -#if defined(LED_PIN) && LED_PIN == -1 -#undef LED_PIN +#if defined(LED_POWER) && LED_POWER == -1 +#undef LED_POWER #else -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #endif #define USE_RF95 diff --git a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini index a6b9d2254..235ac7007 100644 --- a/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini +++ b/variants/esp32/tlora_v2_1_16_tcxo/platformio.ini @@ -7,4 +7,5 @@ build_flags = -D TLORA_V2_1_16 -I variants/esp32/tlora_v2_1_16 -D LORA_TCXO_GPIO=33 -upload_speed = 115200 \ No newline at end of file + -ULED_BUILTIN +upload_speed = 115200 diff --git a/variants/esp32/tlora_v2_1_18/variant.h b/variants/esp32/tlora_v2_1_18/variant.h index efc676992..1ab08c364 100644 --- a/variants/esp32/tlora_v2_1_18/variant.h +++ b/variants/esp32/tlora_v2_1_18/variant.h @@ -6,7 +6,7 @@ #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 -#define LED_PIN 25 // If defined we will blink this LED +#define LED_POWER 25 // If defined we will blink this LED #define BUTTON_PIN 12 // If defined, this will be used for user button presses, #define BUTTON_NEED_PULLUP diff --git a/variants/esp32/trackerd/variant.h b/variants/esp32/trackerd/variant.h index 0996e85ac..8071ba99d 100644 --- a/variants/esp32/trackerd/variant.h +++ b/variants/esp32/trackerd/variant.h @@ -8,7 +8,7 @@ #define GPS_RX_PIN 9 #define GPS_TX_PIN 10 -#define LED_PIN 13 // 13 red, 2 blue, 15 red +#define LED_POWER 13 // 13 red, 2 blue, 15 red #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32/wiphone/variant.h b/variants/esp32/wiphone/variant.h index 619ac622a..5baeb3936 100644 --- a/variants/esp32/wiphone/variant.h +++ b/variants/esp32/wiphone/variant.h @@ -26,7 +26,6 @@ #undef GPS_TX_PIN #define NO_GPS 1 #define HAS_GPS 0 -#define NO_SCREEN #define HAS_SCREEN 0 // Default SPI1 will be mapped to the display diff --git a/variants/esp32c3/ai-c3/variant.h b/variants/esp32c3/ai-c3/variant.h index 6c4f4d38a..a933de76b 100644 --- a/variants/esp32c3/ai-c3/variant.h +++ b/variants/esp32c3/ai-c3/variant.h @@ -4,7 +4,7 @@ #define I2C_SCL SCL #define BUTTON_PIN 9 // BOOT button -#define LED_PIN 30 // RGB LED +#define LED_POWER 30 // RGB LED #define USE_RF95 #define LORA_SCK 4 diff --git a/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h b/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h index 7432a9941..71090fbeb 100644 --- a/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h +++ b/variants/esp32c3/hackerboxes_esp32c3_oled/variant.h @@ -3,7 +3,7 @@ // Hackerboxes LoRa ESP32-C3 OLED Kit // Uses a ESP32-C3 OLED Board and a RA-01SH (SX1262) LoRa Board -#define LED_PIN 8 // LED +#define LED_POWER 8 // LED #define LED_STATE_ON 1 // State when LED is lit #define HAS_SCREEN 0 diff --git a/variants/esp32c3/heltec_esp32c3/variant.h b/variants/esp32c3/heltec_esp32c3/variant.h index ca00c43fa..ed2f6f878 100644 --- a/variants/esp32c3/heltec_esp32c3/variant.h +++ b/variants/esp32c3/heltec_esp32c3/variant.h @@ -3,7 +3,7 @@ // LED pin on HT-DEV-ESP_V2 and HT-DEV-ESP_V3 // https://resource.heltec.cn/download/HT-CT62/HT-CT62_Reference_Design.pdf // https://resource.heltec.cn/download/HT-DEV-ESP/HT-DEV-ESP_V3_Sch.pdf -#define LED_PIN 2 // LED +#define LED_POWER 2 // LED #define LED_STATE_ON 1 // State when LED is lit #define HAS_SCREEN 0 diff --git a/variants/esp32c6/m5stack_unitc6l/platformio.ini b/variants/esp32c6/m5stack_unitc6l/platformio.ini index 37221c103..4c04d0c26 100644 --- a/variants/esp32c6/m5stack_unitc6l/platformio.ini +++ b/variants/esp32c6/m5stack_unitc6l/platformio.ini @@ -43,4 +43,4 @@ lib_ignore = NonBlockingRTTTL libpax build_src_filter = - ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l> \ No newline at end of file + ${esp32c6_base.build_src_filter} +<../variants/esp32c6/m5stack_unitc6l> diff --git a/variants/esp32c6/tlora_c6/variant.h b/variants/esp32c6/tlora_c6/variant.h index 4a0d40232..fcc5b9813 100644 --- a/variants/esp32c6/tlora_c6/variant.h +++ b/variants/esp32c6/tlora_c6/variant.h @@ -1,7 +1,7 @@ #define I2C_SDA 8 // I2C pins for this board #define I2C_SCL 9 -#define LED_PIN 7 // If defined we will blink this LED +#define LED_POWER 7 // If defined we will blink this LED #define LED_STATE_ON 0 // State when LED is lit #define USE_SX1262 diff --git a/variants/esp32s2/nugget_s2_lora/variant.h b/variants/esp32s2/nugget_s2_lora/variant.h index 2d123d603..9d88882e5 100644 --- a/variants/esp32s2/nugget_s2_lora/variant.h +++ b/variants/esp32s2/nugget_s2_lora/variant.h @@ -1,7 +1,7 @@ #define I2C_SDA 34 // I2C pins for this board #define I2C_SCL 36 -#define LED_PIN 15 // If defined we will blink this LED +#define LED_POWER 15 // If defined we will blink this LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 3 // How many neopixels are connected diff --git a/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h b/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h index 1591f6395..5decc7eb2 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h +++ b/variants/esp32s3/CDEBYTE_EoRa-Hub/variant.h @@ -1,7 +1,7 @@ // EByte EoRA-Hub // Uses E80 (LR1121) LoRa module -#define LED_PIN 35 +#define LED_POWER 35 // Button - user interface #define BUTTON_PIN 0 // BOOT button diff --git a/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h index 5da99667b..85321cbe0 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h +++ b/variants/esp32s3/CDEBYTE_EoRa-S3/variant.h @@ -1,5 +1,5 @@ // LED - status indication -#define LED_PIN 37 +#define LED_POWER 37 // Button - user interface #define BUTTON_PIN 0 // This is the BOOT button, and it has its own pull-up resistor diff --git a/variants/esp32s3/EBYTE_ESP32-S3/variant.h b/variants/esp32s3/EBYTE_ESP32-S3/variant.h index 80fb26434..6dbe6231c 100644 --- a/variants/esp32s3/EBYTE_ESP32-S3/variant.h +++ b/variants/esp32s3/EBYTE_ESP32-S3/variant.h @@ -100,7 +100,7 @@ */ // Status -#define LED_PIN 1 +#define LED_POWER 1 #define LED_STATE_ON 1 // State when LED is lit // External notification // FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the diff --git a/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h index ff4f883fe..8768fd70e 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M2/variant.h @@ -1,5 +1,5 @@ // Status -#define LED_PIN 1 +#define LED_POWER 1 #define PIN_BUTTON1 47 // 功能键 #define PIN_BUTTON2 4 // 电源键 diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp index ac480f83c..917341560 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.cpp @@ -8,7 +8,13 @@ void earlyInitVariant() Wire1.begin(48, 47); io.pinMode(PCA_PIN_EINK_EN, OUTPUT); io.pinMode(PCA_PIN_POWER_EN, OUTPUT); + io.pinMode(PCA_LED_POWER, OUTPUT); + io.pinMode(PCA_LED_USER, OUTPUT); + io.pinMode(PCA_LED_ENABLE, OUTPUT); + io.digitalWrite(PCA_PIN_POWER_EN, HIGH); + io.digitalWrite(PCA_LED_USER, LOW); + io.digitalWrite(PCA_LED_ENABLE, LOW); } void variant_shutdown() diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h index 6befac580..223f60264 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -8,8 +8,10 @@ // LED // Both of these are on the GPIO expander -#define PCA_LED_USER 1 // the Blue LED -#define PCA_LED_POWER 3 // the Red LED? Seems to have hardware logic to blink when USB is plugged in. +#define PCA_LED_USER 1 // the Blue LED +#define PCA_LED_ENABLE 2 // the power supply to the LEDs, in an OR arrangement with VBUS power +#define PCA_LED_POWER 3 // the Red LED? Seems to have hardware logic to blink when USB is plugged in. +#define POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING // USB_CHECK #define EXT_PWR_DETECT 12 diff --git a/variants/esp32s3/bpi_picow_esp32_s3/variant.h b/variants/esp32s3/bpi_picow_esp32_s3/variant.h index d8d9413d7..d3e573645 100644 --- a/variants/esp32s3/bpi_picow_esp32_s3/variant.h +++ b/variants/esp32s3/bpi_picow_esp32_s3/variant.h @@ -11,7 +11,7 @@ #define I2C_SDA 12 #define I2C_SCL 14 -#define LED_PIN 46 +#define LED_POWER 46 #define LED_STATE_ON 0 // State when LED is litted // #define BUTTON_PIN 15 // Pico OLED 1.3 User key 0 - removed User key 1 (17) diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h index 360e33481..c9200b96b 100644 --- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h +++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/variant.h @@ -26,7 +26,7 @@ // #define GPS_RX_PIN 44 // #define GPS_TX_PIN 43 -#define LED_PIN 41 +#define LED_POWER 41 #define BUTTON_PIN 2 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h b/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h index 024f912dd..54db932ea 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h +++ b/variants/esp32s3/diy/my_esp32s3_diy_eink/variant.h @@ -11,7 +11,7 @@ #define I2C_SDA 18 // 1 // I2C pins for this board #define I2C_SCL 17 // 2 -// #define LED_PIN 38 // This is a RGB LED not a standard LED +// #define LED_POWER 38 // This is a RGB LED not a standard LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected #define NEOPIXEL_DATA 38 // gpio pin used to send data to the neopixels diff --git a/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h b/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h index 8a3a39003..20e058058 100644 --- a/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h +++ b/variants/esp32s3/diy/my_esp32s3_diy_oled/variant.h @@ -11,7 +11,7 @@ #define I2C_SDA 18 // 1 // I2C pins for this board #define I2C_SCL 17 // 2 -// #define LED_PIN 38 // This is a RGB LED not a standard LED +// #define LED_POWER 38 // This is a RGB LED not a standard LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected #define NEOPIXEL_DATA 38 // gpio pin used to send data to the neopixels diff --git a/variants/esp32s3/dreamcatcher/variant.h b/variants/esp32s3/dreamcatcher/variant.h index 7835979e1..963aff477 100644 --- a/variants/esp32s3/dreamcatcher/variant.h +++ b/variants/esp32s3/dreamcatcher/variant.h @@ -6,7 +6,7 @@ #define I2C_SDA1 45 #define I2C_SCL1 46 -#define LED_PIN 6 +#define LED_POWER 6 #define LED_STATE_ON 1 #define BUTTON_PIN 0 diff --git a/variants/esp32s3/esp32-s3-pico/variant.h b/variants/esp32s3/esp32-s3-pico/variant.h index bfcb6059d..65732171a 100644 --- a/variants/esp32s3/esp32-s3-pico/variant.h +++ b/variants/esp32s3/esp32-s3-pico/variant.h @@ -8,7 +8,6 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 0 // 17 -// #define LED_PIN PIN_LED // Board has RGB LED 21 #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 1 // How many neopixels are connected diff --git a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h index b30b7fc3e..3ee5545a8 100644 --- a/variants/esp32s3/heltec_capsule_sensor_v3/variant.h +++ b/variants/esp32s3/heltec_capsule_sensor_v3/variant.h @@ -1,5 +1,5 @@ -#define LED_PIN 33 -#define LED_PIN2 34 +#define LED_POWER 33 +#define LED_POWER2 34 #define EXT_PWR_DETECT 35 #define BUTTON_PIN 18 diff --git a/variants/esp32s3/heltec_v3/variant.h b/variants/esp32s3/heltec_v3/variant.h index d760c3b7f..d2d904d9c 100644 --- a/variants/esp32s3/heltec_v3/variant.h +++ b/variants/esp32s3/heltec_v3/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN LED +#define LED_POWER LED #define USE_SSD1306 // Heltec_v3 has a SSD1306 display diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini index 6daf9a317..72c53ded0 100644 --- a/variants/esp32s3/heltec_v4/platformio.ini +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -26,7 +26,7 @@ build_flags = ${heltec_v4_base.build_flags} -D HELTEC_V4_OLED -D USE_SSD1306 ; Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver) - -D LED_PIN=35 + -D LED_POWER=35 -D RESET_OLED=21 -D I2C_SDA=17 -D I2C_SCL=18 diff --git a/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h b/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h index 1b1291424..fb0744bc3 100644 --- a/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h +++ b/variants/esp32s3/heltec_vision_master_e213/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -87,6 +88,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 @@ -114,4 +116,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/heltec_vision_master_e213/variant.h b/variants/esp32s3/heltec_vision_master_e213/variant.h index 60f4e00cc..c9aaa2ee8 100644 --- a/variants/esp32s3/heltec_vision_master_e213/variant.h +++ b/variants/esp32s3/heltec_vision_master_e213/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 45 // LED is not populated on earliest board variant +#define LED_POWER 45 // LED is not populated on earliest board variant #define BUTTON_PIN 0 #define PIN_BUTTON2 21 // Second built-in button #define ALT_BUTTON_PIN PIN_BUTTON2 // Send the up event diff --git a/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h b/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h index 61b08c740..a90500b15 100644 --- a/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h +++ b/variants/esp32s3/heltec_vision_master_e290/nicheGraphics.h @@ -24,6 +24,7 @@ Different NicheGraphics UIs and different hardware variants will each have their // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -84,6 +85,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 @@ -111,4 +113,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/heltec_vision_master_e290/variant.h b/variants/esp32s3/heltec_vision_master_e290/variant.h index d7bae7dc2..b32715e39 100644 --- a/variants/esp32s3/heltec_vision_master_e290/variant.h +++ b/variants/esp32s3/heltec_vision_master_e290/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 45 // LED is not populated on earliest board variant +#define LED_POWER 45 // LED is not populated on earliest board variant #define BUTTON_PIN 0 #define PIN_BUTTON2 21 // Second built-in button #define ALT_BUTTON_PIN PIN_BUTTON2 // Send the up event diff --git a/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h b/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h index 445b57714..9e84a541e 100644 --- a/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h +++ b/variants/esp32s3/heltec_wireless_paper/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -87,6 +88,7 @@ void setupNicheGraphics() inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // - // Start running InkHUD inkhud->begin(); @@ -107,4 +109,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/heltec_wireless_paper/variant.h b/variants/esp32s3/heltec_wireless_paper/variant.h index bbfd54ada..7f57bb67f 100644 --- a/variants/esp32s3/heltec_wireless_paper/variant.h +++ b/variants/esp32s3/heltec_wireless_paper/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define BUTTON_PIN 0 // I2C diff --git a/variants/esp32s3/heltec_wireless_paper_v1/variant.h b/variants/esp32s3/heltec_wireless_paper_v1/variant.h index 4505395c9..59dd485f6 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/variant.h +++ b/variants/esp32s3/heltec_wireless_paper_v1/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define BUTTON_PIN 0 // I2C diff --git a/variants/esp32s3/heltec_wireless_tracker/variant.h b/variants/esp32s3/heltec_wireless_tracker/variant.h index 3b19f5afd..b40e40011 100644 --- a/variants/esp32s3/heltec_wireless_tracker/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define _VARIANT_HELTEC_WIRELESS_TRACKER #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h index df5ab4716..e7d3f93c1 100644 --- a/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_V1_0/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h index a5489173d..0ca2dfc03 100644 --- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h +++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define _VARIANT_HELTEC_WIRELESS_TRACKER diff --git a/variants/esp32s3/heltec_wsl_v3/variant.h b/variants/esp32s3/heltec_wsl_v3/variant.h index c103b9172..c81f45d3b 100644 --- a/variants/esp32s3/heltec_wsl_v3/variant.h +++ b/variants/esp32s3/heltec_wsl_v3/variant.h @@ -1,7 +1,7 @@ #define I2C_SCL SCL #define I2C_SDA SDA -#define LED_PIN LED +#define LED_POWER LED #define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost #define VEXT_ON_VALUE LOW diff --git a/variants/esp32s3/mesh-tab/variant.h b/variants/esp32s3/mesh-tab/variant.h index 99204bba3..30042b90f 100644 --- a/variants/esp32s3/mesh-tab/variant.h +++ b/variants/esp32s3/mesh-tab/variant.h @@ -13,7 +13,7 @@ #define ADC_CHANNEL ADC1_GPIO4_CHANNEL // LED -#define LED_PIN 21 +#define LED_POWER 21 // Button #define BUTTON_PIN 0 diff --git a/variants/esp32s3/nibble_esp32/variant.h b/variants/esp32s3/nibble_esp32/variant.h index 8ffbd9d59..8d75a4fbf 100644 --- a/variants/esp32s3/nibble_esp32/variant.h +++ b/variants/esp32s3/nibble_esp32/variant.h @@ -1,7 +1,7 @@ #define I2C_SDA 11 // I2C pins for this board #define I2C_SCL 10 -#define LED_PIN 1 // If defined we will blink this LED +#define LED_POWER 1 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/nugget_s3_lora/variant.h b/variants/esp32s3/nugget_s3_lora/variant.h index 1354d0837..633ed27f6 100644 --- a/variants/esp32s3/nugget_s3_lora/variant.h +++ b/variants/esp32s3/nugget_s3_lora/variant.h @@ -4,7 +4,7 @@ #define USE_SSD1306 #define DISPLAY_FLIP_SCREEN -#define LED_PIN 15 // If defined we will blink this LED +#define LED_POWER 15 // If defined we will blink this LED #define HAS_NEOPIXEL // Enable the use of neopixels #define NEOPIXEL_COUNT 3 // How many neopixels are connected diff --git a/variants/esp32s3/rak3312/variant.h b/variants/esp32s3/rak3312/variant.h index 6431f1fd0..ee0fff524 100644 --- a/variants/esp32s3/rak3312/variant.h +++ b/variants/esp32s3/rak3312/variant.h @@ -24,7 +24,7 @@ #define PIN_LED1 LED_GREEN #define LED_NOTIFICATION LED_BLUE -#define LED_PIN LED_GREEN +#define LED_POWER LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h index 7d263165c..90cb12053 100644 --- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -32,7 +32,7 @@ #define PIN_LED1 LED_GREEN #define LED_NOTIFICATION LED_BLUE -#define LED_PIN LED_GREEN +#define LED_POWER LED_GREEN #define ledOff(pin) pinMode(pin, INPUT) #define LED_STATE_ON 1 // State when LED is litted diff --git a/variants/esp32s3/seeed_xiao_s3/variant.h b/variants/esp32s3/seeed_xiao_s3/variant.h index d8dcbc8d4..11bf48521 100644 --- a/variants/esp32s3/seeed_xiao_s3/variant.h +++ b/variants/esp32s3/seeed_xiao_s3/variant.h @@ -30,7 +30,7 @@ Expansion Board Infomation : https://www.seeedstudio.com/Seeeduino-XIAO-Expansio L76K GPS Module Information : https://www.seeedstudio.com/L76K-GNSS-Module-for-Seeed-Studio-XIAO-p-5864.html */ -#define LED_PIN 48 +#define LED_POWER 48 #define LED_STATE_ON 1 // State when LED is lit #define BUTTON_PIN 21 // This is the Program Button diff --git a/variants/esp32s3/station-g2/platformio.ini b/variants/esp32s3/station-g2/platformio.ini index 091b35f00..4efb21a00 100755 --- a/variants/esp32s3/station-g2/platformio.ini +++ b/variants/esp32s3/station-g2/platformio.ini @@ -21,11 +21,11 @@ upload_protocol = esptool upload_speed = 921600 build_unflags = ${esp32s3_base.build_unflags} - -DARDUINO_USB_MODE=1 + -DARDUINO_USB_MODE=0 build_flags = ${esp32s3_base.build_flags} -D STATION_G2 -I variants/esp32s3/station-g2 -DBOARD_HAS_PSRAM -DSTATION_G2 - -DARDUINO_USB_MODE=0 + -DARDUINO_USB_MODE=1 diff --git a/variants/esp32s3/t-beam-1w/variant.h b/variants/esp32s3/t-beam-1w/variant.h index 5b2e868e3..52e99320e 100644 --- a/variants/esp32s3/t-beam-1w/variant.h +++ b/variants/esp32s3/t-beam-1w/variant.h @@ -67,7 +67,7 @@ #endif // LED -#define LED_PIN 18 +#define LED_POWER 18 #define LED_STATE_ON 1 // HIGH = ON // Battery ADC diff --git a/variants/esp32s3/t-eth-elite/variant.h b/variants/esp32s3/t-eth-elite/variant.h index b7ac05872..8f2748c36 100644 --- a/variants/esp32s3/t-eth-elite/variant.h +++ b/variants/esp32s3/t-eth-elite/variant.h @@ -12,7 +12,7 @@ #define HAS_SCREEN 1 // Allow for OLED Screens on I2C Header of shield -#define LED_PIN 38 // If defined we will blink this LED +#define LED_POWER 38 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses, #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h b/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h index 8f5e63653..73cc2e235 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h +++ b/variants/esp32s3/tlora_t3s3_epaper/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -67,6 +68,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 @@ -86,4 +88,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/esp32s3/tlora_t3s3_epaper/variant.h b/variants/esp32s3/tlora_t3s3_epaper/variant.h index 1ed505420..0f4875fc4 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/variant.h +++ b/variants/esp32s3/tlora_t3s3_epaper/variant.h @@ -22,7 +22,7 @@ #define GPS_RX_PIN 44 #define GPS_TX_PIN 43 -#define LED_PIN 37 +#define LED_POWER 37 #define BUTTON_PIN 0 #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/tlora_t3s3_v1/variant.h b/variants/esp32s3/tlora_t3s3_v1/variant.h index babe44a58..02e2a0e42 100644 --- a/variants/esp32s3/tlora_t3s3_v1/variant.h +++ b/variants/esp32s3/tlora_t3s3_v1/variant.h @@ -14,7 +14,7 @@ #define I2C_SDA1 43 #define I2C_SCL1 44 -#define LED_PIN 37 // If defined we will blink this LED +#define LED_POWER 37 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses, #define BUTTON_NEED_PULLUP diff --git a/variants/esp32s3/tracksenger/internal/variant.h b/variants/esp32s3/tracksenger/internal/variant.h index ba3e281c8..f9a20c901 100644 --- a/variants/esp32s3/tracksenger/internal/variant.h +++ b/variants/esp32s3/tracksenger/internal/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/tracksenger/lcd/variant.h b/variants/esp32s3/tracksenger/lcd/variant.h index a9bb89d68..029f7753b 100644 --- a/variants/esp32s3/tracksenger/lcd/variant.h +++ b/variants/esp32s3/tracksenger/lcd/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/tracksenger/oled/variant.h b/variants/esp32s3/tracksenger/oled/variant.h index 689864b32..1f1fbbaa1 100644 --- a/variants/esp32s3/tracksenger/oled/variant.h +++ b/variants/esp32s3/tracksenger/oled/variant.h @@ -1,4 +1,4 @@ -#define LED_PIN 18 +#define LED_POWER 18 #define HELTEC_TRACKER_V1_X diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index f52fcc09a..1d4af52f3 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -38,8 +38,8 @@ build_src_filter = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.19 - # TODO renovate - https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 + # TODO renovate https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0 + https://gitlab.com/hamishcunningham/unphonelibrary/-/archive/meshtastic/unphonelibrary-meshtastic.zip # renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel adafruit/Adafruit NeoPixel@1.15.4 diff --git a/variants/esp32s3/unphone/variant.h b/variants/esp32s3/unphone/variant.h index 6f0710d62..268eedea5 100644 --- a/variants/esp32s3/unphone/variant.h +++ b/variants/esp32s3/unphone/variant.h @@ -54,7 +54,7 @@ #define SD_SPI_FREQUENCY 25000000 -#define LED_PIN 13 // the red part of the RGB LED +#define LED_POWER 13 // the red part of the RGB LED #define LED_STATE_ON 0 // State when LED is lit #define ALT_BUTTON_PIN 21 // Button 3 - square - top button in landscape mode diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index eaf6a0e56..17ec59442 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -41,6 +41,8 @@ build_flags = ${arduino_base.build_flags} -D ARCH_PORTDUINO -fPIC + -D_FORTIFY_SOURCE=2 + -fstack-protector-all -Wstack-protector --param ssp-buffer-size=4 -Isrc/platform/portduino -DRADIOLIB_EEPROM_UNSUPPORTED -DPORTDUINO_LINUX_HARDWARE diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h b/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h index f64de9d07..242e5ae49 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -71,13 +72,14 @@ void setupNicheGraphics() // Pick applets // Note: order of applets determines priority of "auto-show" feature - inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown - inkhud->addApplet("DMs", new InkHUD::DMApplet); // - - inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 + inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown + inkhud->addApplet("DMs", new InkHUD::DMApplet); // - + inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - + inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - + inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - + inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // - // Start running InkHUD inkhud->begin(); @@ -115,4 +117,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h index 4abec3a0a..fa127ae3e 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M3/variant.h @@ -52,7 +52,6 @@ extern "C" { // LED #define LED_RED 33 #define LED_POWER LED_RED -#define LED_CHARGE LED_POWER // Signals the Status LED Module to handle this LED #define LED_GREEN 35 #define LED_NOTIFICATION LED_GREEN #define LED_BLUE 37 diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp index bc0381a48..a43755c06 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(LED_CHARGE, OUTPUT); - ledOff(LED_CHARGE); - pinMode(LED_PAIRING, OUTPUT); ledOff(LED_PAIRING); diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h index cef26db39..2ebb79031 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h +++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h @@ -41,7 +41,7 @@ extern "C" { // LEDs #define LED_BLUE -1 -#define LED_CHARGE (12) +#define LED_POWER (12) #define LED_PAIRING (7) #define LED_NOTIFICATION LED_PAIRING diff --git a/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp b/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp index 35dc1d39b..5972861d6 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp +++ b/variants/nrf52840/ME25LS01-4Y10TD/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); } \ No newline at end of file diff --git a/variants/nrf52840/ME25LS01-4Y10TD/variant.h b/variants/nrf52840/ME25LS01-4Y10TD/variant.h index a920b02e5..1d1af37ad 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD/variant.h @@ -50,7 +50,7 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp index 35dc1d39b..5972861d6 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.cpp @@ -32,9 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); } \ No newline at end of file diff --git a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h index 683669160..a5bb53a33 100644 --- a/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h +++ b/variants/nrf52840/ME25LS01-4Y10TD_e-ink/variant.h @@ -50,7 +50,7 @@ extern "C" { #define PIN_LED1 (32 + 7) // P1.07 Blue D2 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/MS24SF1/variant.h b/variants/nrf52840/MS24SF1/variant.h index a71be99fd..a41b3a350 100644 --- a/variants/nrf52840/MS24SF1/variant.h +++ b/variants/nrf52840/MS24SF1/variant.h @@ -50,7 +50,7 @@ extern "C" { #define PIN_LED1 (-1) -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h index 8f30a244f..0a01b613e 100644 --- a/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h +++ b/variants/nrf52840/diy/nrf52_promicro_diy_tcxo/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -72,7 +73,8 @@ void setupNicheGraphics() inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false, 3); // Default on tile 3 inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 2); // Default on tile 2 inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1 + inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false, 0); // Default on tile 0 inkhud->addApplet("Heard", new InkHUD::HeardApplet, true); // Background @@ -92,4 +94,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md index 194c53434..7fbf83d7c 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/README.md @@ -1,43 +1,21 @@ # XIAO nRF52840 + XIAO Wio SX1262 -For a mere doubling in price you too can swap out the XIAO ESP32C3 for a XIAO nRF52840, stack the Wio SX1262 radio board either above or underneath the nRF52840, solder the pins, and achieve a massive improvement in battery life! +For a mere doubling in price you too can swap out the XIAO ESP32S3 for a XIAO nRF52840, stack the Wio SX1262 radio board either above or underneath the nRF52840, solder the pins, and achieve a massive improvement in battery life! -I'm not really sure why else you would want to as the ESP32C3 is perfectly cromulent, easily connects to the Wio SX1262 via the B2B connector and has an onboard IPEX connector for the included Bluetooth antenna. So you'll also lose BT range, but you will also have working ADC for the battery in Meshtastic and also have an ESP32C3 to use for something else! +I'm not really sure why else you would want to as the ESP32S3 is perfectly cromulent, easily connects to the Wio SX1262 via the B2B connector and has an onboard IPEX connector for the included Bluetooth antenna. So you'll also lose BT range, but you will also have working ADC for the battery in Meshtastic and also have an ESP32S3 to use for something else! If you're still reading you are clearly gonna do it anyway, so...mount the Wio SX1262 either on top or underneath depending on your preference. The `variant.h` will work with either configuration though it does map the Wio SX1262's button to nRF52840 Pin `D5` as it can still be used as a user button and it's nice to be able to gracefully shutdown a node by holding it down for 5 seconds. If you do decide to wire up the button, orient it so looking straight-down at the Wio SX1262 the radio chip is at the bottom, button in the middle and the hole is at the top - the **left** side of the button should be soldered to `GND` (e.g. the 2nd pin down the top on the **right** row of pins) and the **right** side of the button should be soldered to `D5` (e.g. the 2nd pin up from the button on the **left** row of pins.). This mirrors the original wiring and wiring it in reverse could end up connecting GND to voltage and that's no beuno. -Serial Pins remain available on `D6` (TX) and `D7` (RX) should you want to use them, The same pins could be repurposed for `i2c` if you would like to have that instead of serial, in `variant.h` you would just need to change: +Serial Pins remain available on `D6` (TX) and `D7` (RX) should you want to use them, and I2C has been mapped to NFC1 (SDA, D30) and NFC2 (SCL, D31) -```c++ -// RX and TX pins -#define PIN_SERIAL1_RX (6) -#define PIN_SERIAL1_TX (7) +The same pins could be reordered if you would like to have a different arrangement, in `variant.h` you would just need to change the relevant lines: + +```cpp +#define GPS_TX_PIN D6 // This is data from the MCU +#define GPS_RX_PIN D7 // This is data from the GNSS module + +#define PIN_WIRE_SDA D6 +#define PIN_WIRE_SCL D7 ``` - -to - -```c++ -// RX and TX pins -#define PIN_SERIAL1_RX (-1) -#define PIN_SERIAL1_TX (-1) -``` - -and - -```c++ -#define PIN_WIRE_SDA (-1) -#define PIN_WIRE_SCL (-1) -// #define PIN_WIRE_SDA (6) -// #define PIN_WIRE_SCL (7) -``` - -to - -```c++ -#define PIN_WIRE_SDA (6) -#define PIN_WIRE_SCL (7) -``` - -If you wanted both serial and i2c you could even go so far as to use the pads for the PDM mic which is missing on the non-sense board (`P1.00` / `P0.16`)... or move up to the nRF52840 Plus which has even more pins available but hasn't been checked/confirmed if it follows the same pin mapping as the non-plus. diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini index 10eab2aa4..81076bd55 100644 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini +++ b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/platformio.ini @@ -1,13 +1,17 @@ -; Seeed XIAO nRF52840 + XIAO Wio SX1262 DIY -[env:seeed-xiao-nrf52840-wio-sx1262] -board = xiao_ble_sense -extends = nrf52840_base +; Seeed Xiao BLE but using the B2B from ESP32S3 variant +[env:seeed_xiao_nrf52840_btb] +extends = env:seeed_xiao_nrf52840_kit board_level = extra build_flags = ${nrf52840_base.build_flags} - -Ivariants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262 - -D PRIVATE_HW + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit -Isrc/platform/nrf52/softdevice -Isrc/platform/nrf52/softdevice/nrf52 + -DPRIVATE_HW ; Define private hardware + -DSEEED_XIAO_NRF_WIO_BTB ; Define Seeed XIAO nRF Wio B2B + -USEEED_XIAO_NRF52840_KIT ; Remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262> +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink \ No newline at end of file diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp deleted file mode 100644 index 300f69d0b..000000000 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "variant.h" -#include "nrf.h" -#include "wiring_constants.h" -#include "wiring_digital.h" - -const uint32_t g_ADigitalPinMap[] = { - // D0 .. D13 - 2, // D0 is P0.02 (A0) - 3, // D1 is P0.03 (A1) - 28, // D2 is P0.28 (A2) - 29, // D3 is P0.29 (A3) - 4, // D4 is P0.04 (A4,SDA) - 5, // D5 is P0.05 (A5,SCL) - 43, // D6 is P1.11 (TX) - 44, // D7 is P1.12 (RX) - 45, // D8 is P1.13 (SCK) - 46, // D9 is P1.14 (MISO) - 47, // D10 is P1.15 (MOSI) - - // LEDs - 26, // D11 is P0.26 (LED RED) - 6, // D12 is P0.06 (LED BLUE) - 30, // D13 is P0.30 (LED GREEN) - 14, // D14 is P0.14 (READ_BAT) - - // LSM6DS3TR - 40, // D15 is P1.08 (6D_PWR) - 27, // D16 is P0.27 (6D_I2C_SCL) - 7, // D17 is P0.07 (6D_I2C_SDA) - 11, // D18 is P0.11 (6D_INT1) - - // MIC - 42, // 17,//42, // D19 is P1.10 (MIC_PWR) - 32, // 26,//32, // D20 is P1.00 (PDM_CLK) - 16, // 25,//16, // D21 is P0.16 (PDM_DATA) - - // BQ25100 - 13, // D22 is P0.13 (HICHG) - 17, // D23 is P0.17 (~CHG) - - // - 21, // D24 is P0.21 (QSPI_SCK) - 25, // D25 is P0.25 (QSPI_CSN) - 20, // D26 is P0.20 (QSPI_SIO_0 DI) - 24, // D27 is P0.24 (QSPI_SIO_1 DO) - 22, // D28 is P0.22 (QSPI_SIO_2 WP) - 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) - - // NFC - 9, // D30 is P0.09 (NFC1) - 10, // D31 is P0.10 (NFC2) - - // VBAT - 31, // D32 is P0.10 (VBAT) -}; - -void initVariant() -{ - // Set BQ25101 ISET to 100mA instead of 50mA - pinMode(HICHG, OUTPUT); - digitalWrite(HICHG, LOW); -} diff --git a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h b/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h deleted file mode 100644 index 6927f1295..000000000 --- a/variants/nrf52840/diy/seeed-xiao-nrf52840-wio-sx1262/variant.h +++ /dev/null @@ -1,185 +0,0 @@ -// basically xiao_ble with pins remapped for: -// Seeed XIAO nRF52840 : https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html -// Seeed Wio SX1626 : https://www.seeedstudio.com/Wio-SX1262-with-XIAO-ESP32S3-p-5982.html - -#ifndef _SEEED_XIAO_NRF52840_SENSE_H_ -#define _SEEED_XIAO_NRF52840_SENSE_H_ - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -#define USE_LFXO // Board uses 32khz crystal for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#define PINS_COUNT (33) -#define NUM_DIGITAL_PINS (33) -#define NUM_ANALOG_INPUTS (8) // A6 is used for battery, A7 is analog reference -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -// ---- -#define LED_RED 11 -#define LED_BLUE 12 -#define LED_GREEN 13 - -#define PIN_LED1 LED_GREEN -#define PIN_LED2 LED_BLUE -#define PIN_LED3 LED_RED - -#define PIN_LED PIN_LED1 - -#define LED_STATE_ON 1 // State when LED is lit - -// XIAO Wio-SX1262 Shield User button -#define PIN_BUTTON1 5 -#define BUTTON_NEED_PULLUP - -// Digital Pins -// ------------ -#define D0 (0ul) -#define D1 (1ul) -#define D2 (2ul) -#define D3 (3ul) -#define D4 (4ul) -#define D5 (5ul) -#define D6 (6ul) -#define D7 (7ul) -#define D8 (8ul) -#define D9 (9ul) -#define D10 (10ul) - -// Analog Pins -// ----------- -#define PIN_A0 (0) -#define PIN_A1 (1) -#define PIN_A2 (2) -#define PIN_A3 (3) -#define PIN_A4 (4) -#define PIN_A5 (5) -#define PIN_VBAT (32) -#define VBAT_ENABLE (14) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -#define ADC_RESOLUTION 12 - -// Other Pins -// ---------- -#define PIN_NFC1 (30) -#define PIN_NFC2 (31) - -// RX and TX pins -#define PIN_SERIAL1_RX (-1) -#define PIN_SERIAL1_TX (-1) -// complains if not defined -#define PIN_SERIAL2_RX (-1) -#define PIN_SERIAL2_TX (-1) - -// 4 is used as RF_SW and 5 for USR button so... -#define PIN_WIRE_SDA (6) -#define PIN_WIRE_SCL (7) - -static const uint8_t SDA = PIN_WIRE_SDA; -static const uint8_t SCL = PIN_WIRE_SCL; - -// SPI SX1262 -// ---------- -#define SPI_SX1262 -#ifdef SPI_SX1262 -#define SPI_INTERFACES_COUNT 1 - -#define PIN_SPI_MISO (9) -#define PIN_SPI_MOSI (10) -#define PIN_SPI_SCK (8) - -static const uint8_t SS = D3; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -// supported modules list -#define USE_SX1262 - -// common pinouts for SX126X modules -#define SX126X_CS D3 -#define SX126X_DIO1 D0 -#define SX126X_BUSY D1 -#define SX126X_RESET D2 - -// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_RXEN 38 -#define SX126X_TXEN RADIOLIB_NC -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#endif - -// Wire Interfaces -// ------------------- -#define WIRE_INTERFACES_COUNT 1 // 2 - -// Sense version has IMU and PDM Mic -// #define XIAO_SENSE -#ifndef XIAO_SENSE -// 6 DoF IMU -#define PIN_LSM6DS3TR_C_POWER (15) -#define PIN_LSM6DS3TR_C_INT1 (18) -// PDM Interfaces -// --------------- -#define PIN_PDM_PWR (19) -#define PIN_PDM_CLK (20) -#define PIN_PDM_DIN (21) -#endif - -// QSPI Pins -// --------- -#define PIN_QSPI_SCK (24) -#define PIN_QSPI_CS (25) -#define PIN_QSPI_IO0 (26) -#define PIN_QSPI_IO1 (27) -#define PIN_QSPI_IO2 (28) -#define PIN_QSPI_IO3 (29) - -// On-board QSPI Flash -// ------------------- -#define EXTERNAL_FLASH_DEVICES P25Q16H -#define EXTERNAL_FLASH_USE_QSPI - -// Battery -// ------- -// P0_14 = 14 Reads battery voltage from divider on signal board. -// PIN_VBAT is reading voltage divider on XIAO and is program pin 32 / or P0.31 -#define ADC_CTRL VBAT_ENABLE -#define ADC_CTRL_ENABLED LOW -#define BATTERY_SENSE_RESOLUTION_BITS 10 -#define CHARGE_LED 23 // P0_17 = 17 D23 YELLOW CHARGE LED -#define HICHG 22 // P0_13 = 13 D22 Charge-select pin for Lipo for 100 mA instead of default 50mA charge - -// The battery sense is hooked to pin A0 (5) -#define BATTERY_PIN PIN_VBAT // PIN_A0 - -// ratio of voltage divider = 3.0 (R17=1M, R18=510k) -#define ADC_MULTIPLIER 3 // 3.0 + a bit for being optimistic - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif \ No newline at end of file diff --git a/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini b/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini index a5d0aaf8f..c923bbdb7 100644 --- a/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini +++ b/variants/nrf52840/diy/seeed_xiao_nrf52840_e22/platformio.ini @@ -6,7 +6,8 @@ build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M30S -build_unflags = -DGPS_L76K + -USEEED_XIAO_NRF52840_KIT ; remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define ; Seeed XIAO nRF52840 + EBYTE E22-900M33S - Pinout matching Wio-SX1262 (SKU 113010003) [env:seeed_xiao_nrf52840_e22_900m33s] @@ -16,4 +17,5 @@ build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -D PRIVATE_HW -DEBYTE_E22 -DEBYTE_E22_900M33S -build_unflags = -DGPS_L76K + -USEEED_XIAO_NRF52840_KIT ; remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define \ No newline at end of file diff --git a/variants/nrf52840/diy/xiao_ble/platformio.ini b/variants/nrf52840/diy/xiao_ble/platformio.ini index 6c764ea78..42f53f1bf 100644 --- a/variants/nrf52840/diy/xiao_ble/platformio.ini +++ b/variants/nrf52840/diy/xiao_ble/platformio.ini @@ -2,9 +2,55 @@ [env:xiao_ble] extends = env:seeed_xiao_nrf52840_kit board_level = extra -build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 -D PRIVATE_HW -DXIAO_BLE_LEGACY_PINOUT -DEBYTE_E22 - -DEBYTE_E22_900M30S -build_unflags = -DGPS_L76K + -USEEED_XIAO_NRF52840_KIT ; remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; remove default define +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink + +; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893 +[env:xiao_ble_30db] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DPRIVATE_HW ; Define private hardware + -DXIAO_BLE_LEGACY_PINOUT ; Set legacy pinout + -DEBYTE_E22_900M30S ; Set 30db module + -USEEED_XIAO_NRF52840_KIT ; Remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink + +; Seeed Xiao BLE: https://www.digikey.com/en/products/detail/seeed-technology-co-ltd/102010448/16652893 +[env:xiao_ble_33db] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/seeed_xiao_nrf52840_kit + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DPRIVATE_HW ; Define private hardware + -DXIAO_BLE_LEGACY_PINOUT ; Set legacy pinout + -DEBYTE_E22_900M33S ; Set 33db module + -USEEED_XIAO_NRF52840_KIT ; Remove default HWID + -USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +;upload_protocol = jlink \ No newline at end of file diff --git a/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h b/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h index b6be70ff4..ad17e7457 100644 --- a/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h +++ b/variants/nrf52840/heltec_mesh_node_t114-inkhud/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -73,7 +74,8 @@ void setupNicheGraphics() inkhud->addApplet("DMs", new InkHUD::DMApplet, true, false, 3); // Default on tile 3 inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, false, 2); // Default on tile 2 inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1 + inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false, 1); // Default on tile 1 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false, 0); // Default on tile 0 inkhud->addApplet("Heard", new InkHUD::HeardApplet, true); // Background @@ -93,4 +95,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h b/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h index 10f628d56..187022ea7 100644 --- a/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h +++ b/variants/nrf52840/heltec_mesh_pocket/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -67,6 +68,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 @@ -87,4 +89,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h index 125f50590..0f4131916 100644 --- a/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h +++ b/variants/nrf52840/heltec_mesh_solar/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -67,6 +68,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 @@ -87,4 +89,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini index 07d763df3..9b15e668a 100644 --- a/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini +++ b/variants/nrf52840/rak4631_nomadstar_meteor_pro/platformio.ini @@ -24,8 +24,8 @@ build_flags = ${nrf52840_base.build_flags} build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631_nomadstar_meteor_pro> + + lib_deps = ${nrf52840_base.lib_deps} - # TODO renovate - https://github.com/NomadStar-outdoor/IOBoard-RGB-LP5562-Library.git#9c366c8 + # renovate: datasource=git-refs depName=IOBoard-RGB-LP5562-Library packageName=NomadStar-outdoor/IOBoard-RGB-LP5562-Library gitBranch=master + https://github.com/NomadStar-outdoor/IOBoard-RGB-LP5562-Library/archive/9c366c875e1e8103ed97b5d4c09f3878345da80a.zip ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds diff --git a/variants/nrf52840/seeed_solar_node/variant.cpp b/variants/nrf52840/seeed_solar_node/variant.cpp index 994e97ff9..4123944d4 100644 --- a/variants/nrf52840/seeed_solar_node/variant.cpp +++ b/variants/nrf52840/seeed_solar_node/variant.cpp @@ -101,7 +101,6 @@ void initVariant() pinMode(PIN_LED2, OUTPUT); digitalWrite(PIN_LED2, LOW); pinMode(PIN_LED2, OUTPUT); - // digitalWrite(LED_PIN, LOW); pinMode(GPS_EN, OUTPUT); digitalWrite(GPS_EN, HIGH); diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h index 98aeb8700..2a2967f5e 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h @@ -9,8 +9,10 @@ #include "graphics/niche/InkHUD/InkHUD.h" // Applets +#include "graphics/niche/InkHUD/Applets/Examples/UserAppletInputExample/UserAppletInputExample.h" #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -63,6 +65,7 @@ void setupNicheGraphics() inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side + inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Pick applets // Note: order of applets determines priority of "auto-show" feature @@ -74,6 +77,7 @@ void setupNicheGraphics() inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // - // Start running InkHUD inkhud->begin(); diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini index 68be47622..3f9a4f7af 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini @@ -13,21 +13,22 @@ extends = nrf52840_base board = xiao_ble_sense board_level = pr build_flags = ${nrf52840_base.build_flags} - -Ivariants/nrf52840/seeed_xiao_nrf52840_kit - -Isrc/platform/nrf52/softdevice - -Isrc/platform/nrf52/softdevice/nrf52 + -I variants/nrf52840/seeed_xiao_nrf52840_kit + -I src/platform/nrf52/softdevice + -I src/platform/nrf52/softdevice/nrf52 -DSEEED_XIAO_NRF52840_KIT - -DGPS_L76K + -DSEEED_XIAO_NRF_KIT_DEFAULT board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_xiao_nrf52840_kit> debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink -; Seeed Xiao BLE but with GPS undefined, and therefore i2c active +; Seeed Xiao BLE but with GPS moved to NFC pins, and therefore i2c active [env:seeed_xiao_nrf52840_kit_i2c] extends = env:seeed_xiao_nrf52840_kit board_level = extra build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} -DSEEED_XIAO_NRF52840_KIT -build_unflags = -DGPS_L76K + -DSEEED_XIAO_NRF_KIT_I2C ; Define I2C variant + -USEEED_XIAO_NRF_KIT_DEFAULT ; Remove default define \ No newline at end of file diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h index 0d599d313..4dc28557d 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/variant.h @@ -17,6 +17,37 @@ extern "C" { #endif // __cplusplus +/* +Xiao pin assignments + +| Pin | Default | I2C | BTB | BLE-L | | Pin | Default | I2C | BTB | BLE-L | +| ----- | -------- | ---- | ---- | ----- | --- | ----- | ------- | ---- | ---- | ----- | +| | | | | | | | | | | | +| D0 | G_STBY | UBTN | DIO1 | CS | | 5v | | | | | +| D1 | DIO1 | DIO1 | Busy | DIO1 | | GND | | | | | +| D2 | NRST | NRST | NRST | Busy | | 3v3 | | | | | +| D3 | Busy | Busy | CS | NRST | | D10 | MOSI | MOSI | MOSI | MOSI | +| D4 | CS | CS | RXEN | SDA | | D9 | MISO | MISO | MISO | MISO | +| D5 | RXEN | RXEN | | SCL | | D8 | SCK | SCK | SCK | SCK | +| D6 | G_TX | SDA | G_TX | | | D7 | G_RX | SCL | G_RX | RXEN | +| | | | | | | | | | | | +| | End | | | | | | | | | | +| NFC1/ | SDA | G_TX | SDA | G_TX | | NFC2/ | SCL | G_RX | SCL | G_RX | +| D30 | | | | | | D31 | | | | | +| | | | | | | | | | | | +| | Internal | | | | | | | | | | +| D16 | SCL1 | SCL1 | SCL1 | SCL1 | | | | | | | +| D17 | SDA1 | SDA1 | SDA1 | SDA1 | | | | | | | + +The default column shows the pin assignments for the Wio-SX1262 for XIAO +(standalone SKU 113010003 or nRF52840 kit SKU 102010710). +The I2C column shows an alternative pin assignment using I2C on D6/D7 in place of the GNSS. +The BTB column shows the pin assignment for the Wio-SX1262 -30-pin board-to-board connector version from the ESP32S3 kit. +The BLE-L column shows the pin assignment for the original DIY xiao_ble, and which is retained for legacy users. +Note that the in addition to the difference between the default and the I2C pinouts in placing the pins on NFC or +D6/D7, the user button is activated on D0. The button conflicts with the official GNSS module, so caution is advised. +*/ + #define PINS_COUNT (33) #define NUM_DIGITAL_PINS (33) #define NUM_ANALOG_INPUTS (8) @@ -65,7 +96,7 @@ static const uint8_t A5 = PIN_A5; #define LED_GREEN (13) #define LED_BLUE (12) -#define PIN_LED1 LED_GREEN // PIN_LED1 is used in src/platform/nrf52/architecture.h to define LED_PIN +#define PIN_LED1 LED_GREEN // PIN_LED1 is used in src/platform/nrf52/architecture.h to define LED_POWER #define PIN_LED2 LED_BLUE #define PIN_LED3 LED_RED @@ -91,15 +122,15 @@ static const uint8_t A5 = PIN_A5; */ #define USE_SX1262 -#ifdef XIAO_BLE_LEGACY_PINOUT +#if defined(XIAO_BLE_LEGACY_PINOUT) // Legacy xiao_ble variant pinout for third-party SX126x modules e.g. EBYTE E22 #define SX126X_CS D0 #define SX126X_DIO1 D1 #define SX126X_BUSY D2 #define SX126X_RESET D3 #define SX126X_RXEN D7 - -#elif defined(SEEED_XIAO_WIO_BTB) +#else +#if defined(SEEED_XIAO_NRF_WIO_BTB) // Wio-SX1262 for XIAO with 30-pin board-to-board connector // https://files.seeedstudio.com/products/SenseCAP/Wio_SX1262/Schematic_Diagram_Wio-SX1262_for_XIAO.pdf #define SX126X_CS D3 @@ -109,13 +140,15 @@ static const uint8_t A5 = PIN_A5; #define SX126X_RXEN D4 #else // Wio-SX1262 for XIAO (standalone SKU 113010003 or nRF52840 kit SKU 102010710) +// Same for both default and I2C pinouts // https://files.seeedstudio.com/products/SenseCAP/Wio_SX1262/Wio-SX1262%20for%20XIAO%20V1.0_SCH.pdf #define SX126X_CS D4 #define SX126X_DIO1 D1 #define SX126X_BUSY D3 #define SX126X_RESET D2 #define SX126X_RXEN D5 -#endif +#endif // defined(SEEED_XIAO_NRF_WIO_BTB) +#endif // defined(XIAO_BLE_LEGACY_PINOUT) // Common pinouts for all SX126x pinouts above #define SX126X_TXEN RADIOLIB_NC @@ -141,18 +174,26 @@ static const uint8_t SCK = PIN_SPI_SCK; * GPS */ // GPS L76K -#ifdef GPS_L76K + +// Default GPS L76K +#if defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB) +#define GPS_L76K #define GPS_TX_PIN D6 // This is data from the MCU #define GPS_RX_PIN D7 // This is data from the GNSS module +#if defined(SEEED_XIAO_NRF_KIT_DEFAULT) +#define PIN_GPS_STANDBY D0 // this is where the conflicting pinouts come from +#endif +// I2C and BLE-Legacy put them on the NFC pins +#else +#define GPS_TX_PIN (30) +#define GPS_RX_PIN (31) +#endif + #define HAS_GPS 1 +#define GPS_BAUDRATE 9600 #define GPS_THREAD_INTERVAL 50 #define PIN_SERIAL1_TX GPS_TX_PIN #define PIN_SERIAL1_RX GPS_RX_PIN -#define PIN_GPS_STANDBY D0 -#else -#define PIN_SERIAL1_RX (-1) -#define PIN_SERIAL1_TX (-1) -#endif /* * Battery @@ -171,39 +212,60 @@ static const uint8_t SCK = PIN_SPI_SCK; * Wire Interfaces * Keep this section after potentially conflicting pin definitions */ -#define I2C_NO_RESCAN // I2C is a bit finicky, don't scan too much -#define WIRE_INTERFACES_COUNT 1 +#define I2C_NO_RESCAN // I2C is a bit finicky, don't scan too much +#define WIRE_INTERFACES_COUNT 1 // changed to 1 for now, as LSM6DS3TR has issues. #if defined(XIAO_BLE_LEGACY_PINOUT) // Used for I2C by DIY xiao_ble variant #define PIN_WIRE_SDA D4 #define PIN_WIRE_SCL D5 -#elif !defined(GPS_L76K) -// If D6 and D7 are free, I2C is probably the most versatile assignment +#else +// Put the I2C pins on the NFC pins by default +#if defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB) +#define PIN_WIRE_SDA 30 +#define PIN_WIRE_SCL 31 +#else +// If not on legacy or defauly, we're wanting I2C on the back pins #define PIN_WIRE_SDA D6 #define PIN_WIRE_SCL D7 -#else -// Internal LSM6DS3TR on XIAO nRF52840 Series -#define PIN_WIRE_SDA (17) -#define PIN_WIRE_SCL (16) -#endif +#endif // defined(SEEED_XIAO_NRF_KIT_DEFAULT) || defined(SEEED_XIAO_NRF_WIO_BTB) +#endif // defined(XIAO_BLE_LEGACY_PINOUT) -static const uint8_t SDA = PIN_WIRE_SDA; -static const uint8_t SCL = PIN_WIRE_SCL; +// // Internal LSM6DS3TR on XIAO nRF52840 Series - put it on wire1 +// // Note: disabled for now, as there are some issues with the LSM. +// #define PIN_WIRE1_SDA (17) +// #define PIN_WIRE1_SCL (16) + +static const uint8_t SDA = PIN_WIRE_SDA; // Not sure if this is needed +static const uint8_t SCL = PIN_WIRE_SCL; // Not sure if this is needed + +// // QSPI Pins +// // --------- +// #define PIN_QSPI_SCK (24) +// #define PIN_QSPI_CS (25) +// #define PIN_QSPI_IO0 (26) +// #define PIN_QSPI_IO1 (27) +// #define PIN_QSPI_IO2 (28) +// #define PIN_QSPI_IO3 (29) + +// // On-board QSPI Flash +// // ------------------- +// #define EXTERNAL_FLASH_DEVICES P25Q16H +// #define EXTERNAL_FLASH_USE_QSPI /* * Buttons * Keep this section after potentially conflicting pin definitions * because D0 has multiple possible conflicts with various XIAO modules: - * - PIN_GPS_STANDBY on the L76K GNSS Module - * - DIO1 on the Wio-SX1262 - 30-pin board-to-board connector version - * - SX1262X CS on XIAO BLE legacy pinout */ - -#if !defined(GPS_L76K) && !defined(SEEED_XIAO_WIO_BTB) && !defined(XIAO_BLE_LEGACY_PINOUT) +#if defined(SEEED_XIAO_NRF_KIT_I2C) #define BUTTON_PIN D0 #endif +#if defined(SEEED_XIAO_NRF_WIO_BTB) +#define BUTTON_PIN D5 +#endif + #ifdef __cplusplus } #endif diff --git a/variants/nrf52840/t-echo-lite/variant.h b/variants/nrf52840/t-echo-lite/variant.h index 7dc1a3ef5..54c7bdfb5 100644 --- a/variants/nrf52840/t-echo-lite/variant.h +++ b/variants/nrf52840/t-echo-lite/variant.h @@ -51,7 +51,6 @@ extern "C" { #define LED_GREEN PIN_LED1 #define BLE_LED LED_BLUE -#define BLE_LED_INVERTED 1 #define LED_STATE_ON 0 // State when LED is lit // Buttons diff --git a/variants/nrf52840/t-echo-plus/nicheGraphics.h b/variants/nrf52840/t-echo-plus/nicheGraphics.h index 483e16ea4..73067d7a7 100644 --- a/variants/nrf52840/t-echo-plus/nicheGraphics.h +++ b/variants/nrf52840/t-echo-plus/nicheGraphics.h @@ -8,6 +8,7 @@ #include "graphics/niche/Drivers/EInk/GDEY0154D67.h" #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -43,6 +44,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); diff --git a/variants/nrf52840/t-echo/nicheGraphics.h b/variants/nrf52840/t-echo/nicheGraphics.h index c89d816b9..c0b24dea7 100644 --- a/variants/nrf52840/t-echo/nicheGraphics.h +++ b/variants/nrf52840/t-echo/nicheGraphics.h @@ -11,6 +11,7 @@ // Applets #include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" #include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h" #include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" #include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" #include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" @@ -79,6 +80,7 @@ void setupNicheGraphics() inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // - inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // - inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 @@ -123,4 +125,4 @@ void setupNicheGraphics() buttons->start(); } -#endif \ No newline at end of file +#endif diff --git a/variants/nrf52840/tracker-t1000-e/variant.cpp b/variants/nrf52840/tracker-t1000-e/variant.cpp index 8096705d0..1d0423b9b 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.cpp +++ b/variants/nrf52840/tracker-t1000-e/variant.cpp @@ -32,10 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index 143b7d4ad..e258f63ae 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -47,7 +47,7 @@ extern "C" { #define PIN_3V3_ACC_EN (32 + 7) // P1.7, Power to Acc #define PIN_LED1 (0 + 24) // P0.24 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/nrf52840/wio-t1000-s/variant.cpp b/variants/nrf52840/wio-t1000-s/variant.cpp index 85e0c44f3..3c54f6ce5 100644 --- a/variants/nrf52840/wio-t1000-s/variant.cpp +++ b/variants/nrf52840/wio-t1000-s/variant.cpp @@ -32,10 +32,6 @@ const uint32_t g_ADigitalPinMap[] = { void initVariant() { - // LED1 & LED2 - pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, LOW); - pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); diff --git a/variants/nrf52840/wio-t1000-s/variant.h b/variants/nrf52840/wio-t1000-s/variant.h index 3b8103d85..a55c42775 100644 --- a/variants/nrf52840/wio-t1000-s/variant.h +++ b/variants/nrf52840/wio-t1000-s/variant.h @@ -47,7 +47,7 @@ extern "C" { #define PIN_3V3_ACC_EN (32 + 7) // P1.7, Power to Acc #define PIN_LED1 (0 + 24) // P0.24 -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define LED_BLUE -1 // Actually green #define LED_STATE_ON 1 // State when LED is lit diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h index 5e7311413..ee86e1a34 100644 --- a/variants/rp2040/challenger_2040_lora/pins_arduino.h +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -7,7 +7,7 @@ #define ADC_RESOLUTION (12u) // LEDs -#define PIN_LED (24u) +#define LED_POWER (24u) // Serial #define PIN_SERIAL1_TX (16u) diff --git a/variants/rp2040/challenger_2040_lora/variant.h b/variants/rp2040/challenger_2040_lora/variant.h index 552f90720..f5126cfff 100644 --- a/variants/rp2040/challenger_2040_lora/variant.h +++ b/variants/rp2040/challenger_2040_lora/variant.h @@ -5,8 +5,6 @@ #define EXT_NOTIFY_OUT 0xFFFFFFFF #define BUTTON_PIN 0xFFFFFFFF -#define LED_PIN PIN_LED - #define USE_RF95 // RFM95/SX127x #undef LORA_SCK diff --git a/variants/rp2040/ec_catsniffer/variant.h b/variants/rp2040/ec_catsniffer/variant.h index 400074e59..7df69f134 100644 --- a/variants/rp2040/ec_catsniffer/variant.h +++ b/variants/rp2040/ec_catsniffer/variant.h @@ -9,7 +9,7 @@ #undef GPS_RX_PIN #undef GPS_TX_PIN -#define LED_PIN 27 +#define LED_POWER 27 #define USE_SX1262 diff --git a/variants/rp2040/feather_rp2040_rfm95/variant.h b/variants/rp2040/feather_rp2040_rfm95/variant.h index e9e178202..efaced7b4 100644 --- a/variants/rp2040/feather_rp2040_rfm95/variant.h +++ b/variants/rp2040/feather_rp2040_rfm95/variant.h @@ -20,7 +20,7 @@ #define BUTTON_PIN 7 // #define BUTTON_NEED_PULLUP -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED // #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/nibble_rp2040/variant.h b/variants/rp2040/nibble_rp2040/variant.h index 0f71b98e9..3b1dfcd7b 100644 --- a/variants/rp2040/nibble_rp2040/variant.h +++ b/variants/rp2040/nibble_rp2040/variant.h @@ -2,7 +2,7 @@ #define BUTTON_PIN -1 // Pin 17 used for antenna switching via DIO4 -#define LED_PIN 1 +#define LED_POWER 1 #define HAS_CPU_SHUTDOWN 1 diff --git a/variants/rp2040/rak11310/pins_arduino.h b/variants/rp2040/rak11310/pins_arduino.h index 56214a947..59290bbdb 100644 --- a/variants/rp2040/rak11310/pins_arduino.h +++ b/variants/rp2040/rak11310/pins_arduino.h @@ -23,8 +23,7 @@ static const uint8_t A2 = PIN_A2; static const uint8_t A3 = PIN_A3; // LEDs -#define PIN_LED (23u) -#define PIN_LED1 PIN_LED +#define PIN_LED1 (23u) #define LED_NOTIFICATION (24u) #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/rak11310/variant.h b/variants/rp2040/rak11310/variant.h index 4d2b9ca3a..4dfad060e 100644 --- a/variants/rp2040/rak11310/variant.h +++ b/variants/rp2040/rak11310/variant.h @@ -10,7 +10,7 @@ #define I2C_SDA1 2 #define I2C_SCL1 3 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #define BUTTON_PIN 9 diff --git a/variants/rp2040/rp2040-lora/variant.h b/variants/rp2040/rp2040-lora/variant.h index 92b067457..51a760e0b 100644 --- a/variants/rp2040/rp2040-lora/variant.h +++ b/variants/rp2040/rp2040-lora/variant.h @@ -17,7 +17,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN -1 // Pin 17 used for antenna switching via DIO4 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED // #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/rpipico-slowclock/variant.h b/variants/rp2040/rpipico-slowclock/variant.h index fb97ec0fb..40d20e17a 100644 --- a/variants/rp2040/rpipico-slowclock/variant.h +++ b/variants/rp2040/rpipico-slowclock/variant.h @@ -52,7 +52,7 @@ #define BUTTON_PIN 18 #define EXT_NOTIFY_OUT 22 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/rpipico/variant.h b/variants/rp2040/rpipico/variant.h index 7efaeaf7a..dd849c290 100644 --- a/variants/rp2040/rpipico/variant.h +++ b/variants/rp2040/rpipico/variant.h @@ -15,7 +15,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/rpipicow/variant.h b/variants/rp2040/rpipicow/variant.h index fe94e615d..2de00545e 100644 --- a/variants/rp2040/rpipicow/variant.h +++ b/variants/rp2040/rpipicow/variant.h @@ -19,7 +19,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/rp2040/senselora_rp2040/pins_arduino.h b/variants/rp2040/senselora_rp2040/pins_arduino.h index 61705c8d9..575839cbc 100644 --- a/variants/rp2040/senselora_rp2040/pins_arduino.h +++ b/variants/rp2040/senselora_rp2040/pins_arduino.h @@ -11,8 +11,7 @@ static const uint8_t A2 = PIN_A2; static const uint8_t A3 = PIN_A3; // LEDs -#define PIN_LED (23u) -#define PIN_LED1 PIN_LED +#define PIN_LED1 (23u) #define ADC_RESOLUTION 12 diff --git a/variants/rp2040/senselora_rp2040/variant.h b/variants/rp2040/senselora_rp2040/variant.h index cc90284b7..f79ed66ca 100644 --- a/variants/rp2040/senselora_rp2040/variant.h +++ b/variants/rp2040/senselora_rp2040/variant.h @@ -5,7 +5,7 @@ #define BUTTON_PIN 2 #define BUTTON_NEED_PULLUP -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED1 #define ledOff(pin) pinMode(pin, INPUT) #undef BATTERY_PIN diff --git a/variants/rp2350/rpipico2/variant.h b/variants/rp2350/rpipico2/variant.h index 7efaeaf7a..dd849c290 100644 --- a/variants/rp2350/rpipico2/variant.h +++ b/variants/rp2350/rpipico2/variant.h @@ -15,7 +15,7 @@ #define EXT_NOTIFY_OUT 22 #define BUTTON_PIN 17 -#define LED_PIN PIN_LED +#define LED_POWER PIN_LED #define BATTERY_PIN 26 // ratio of voltage divider = 3.0 (R17=200k, R18=100k) diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index 2cf355a61..686326137 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -15,8 +15,8 @@ Do not expect a working Meshtastic device with this target. #define USE_STM32WLx -#define LED_PIN PB4 // LED1 -// #define LED_PIN PB3 // LED2 +#define LED_POWER PB4 // LED1 +// #define LED_POWER PB3 // LED2 #define LED_STATE_ON 1 #define SERIAL_PRINT_PORT 1 diff --git a/variants/stm32/milesight_gs301/variant.h b/variants/stm32/milesight_gs301/variant.h index e86e93fc4..f68d70458 100644 --- a/variants/stm32/milesight_gs301/variant.h +++ b/variants/stm32/milesight_gs301/variant.h @@ -6,7 +6,7 @@ // I/O #define LED_STATE_ON 1 #define PIN_LED1 PA0 // Green LED -#define LED_PIN PIN_LED1 +#define LED_POWER PIN_LED1 #define PIN_LED2 PA0 // Red LED #define USER_LED PIN_LED2 #define BUTTON_PIN PC13 diff --git a/variants/stm32/rak3172/variant.h b/variants/stm32/rak3172/variant.h index b3f6cbcda..bd6decd4c 100644 --- a/variants/stm32/rak3172/variant.h +++ b/variants/stm32/rak3172/variant.h @@ -13,7 +13,7 @@ Do not expect a working Meshtastic device with this target. #define USE_STM32WLx -#define LED_PIN PA0 // Green LED +#define LED_POWER PA0 // Green LED #define LED_STATE_ON 1 #define RAK3172 diff --git a/variants/stm32/russell/variant.h b/variants/stm32/russell/variant.h index 796302d34..8773d5d8d 100644 --- a/variants/stm32/russell/variant.h +++ b/variants/stm32/russell/variant.h @@ -4,7 +4,7 @@ #define USE_STM32WLx // I/O -#define LED_PIN PA0 // Red LED +#define LED_POWER PA0 // Red LED #define LED_STATE_ON 1 #define BUTTON_PIN PH3 // Shared with BOOT0 #define BUTTON_NEED_PULLUP diff --git a/variants/stm32/wio-e5/variant.h b/variants/stm32/wio-e5/variant.h index 2b20eb2a6..da2c623fb 100644 --- a/variants/stm32/wio-e5/variant.h +++ b/variants/stm32/wio-e5/variant.h @@ -14,7 +14,7 @@ Do not expect a working Meshtastic device with this target. #define USE_STM32WLx -#define LED_PIN PB5 +#define LED_POWER PB5 #define LED_STATE_ON 0 #define WIO_E5 From a77ac21e0da6375716dcd1a6cf8b1be59b94d330 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 19 Feb 2026 07:17:50 -0600 Subject: [PATCH 174/223] Trunk --- src/concurrency/Periodic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/concurrency/Periodic.h b/src/concurrency/Periodic.h index dc20e6d56..8576be7ea 100644 --- a/src/concurrency/Periodic.h +++ b/src/concurrency/Periodic.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include "concurrency/OSThread.h" From 15460c8f97727c5c8b14697d4bc13da1da64524b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 17:11:04 -0600 Subject: [PATCH 175/223] Update meshtastic-GxEPD2 digest to c7eb4c3 (#9694) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini | 2 +- variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini | 6 +++--- variants/esp32s3/heltec_vision_master_e213/platformio.ini | 2 +- variants/esp32s3/heltec_vision_master_e290/platformio.ini | 2 +- variants/esp32s3/heltec_wireless_paper/platformio.ini | 2 +- variants/esp32s3/heltec_wireless_paper_v1/platformio.ini | 2 +- variants/esp32s3/tlora_t3s3_epaper/platformio.ini | 2 +- variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini | 2 +- variants/nrf52840/heltec_mesh_pocket/platformio.ini | 4 ++-- variants/nrf52840/heltec_mesh_solar/platformio.ini | 2 +- variants/nrf52840/meshlink/platformio.ini | 2 +- variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini | 2 +- variants/nrf52840/t-echo-lite/platformio.ini | 2 +- variants/nrf52840/t-echo/platformio.ini | 2 +- 14 files changed, 17 insertions(+), 17 deletions(-) diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini index 92d4bd519..9f8c3a871 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini @@ -31,7 +31,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip # renovate: datasource=custom.pio depName=PCA9557-arduino packageName=maxpromer/library/PCA9557-arduino maxpromer/PCA9557-arduino@1.0.0 # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib diff --git a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini index 7a0bd31b4..7e37a0eb4 100644 --- a/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini +++ b/variants/esp32s3/crowpanel-esp32s3-5-epaper/platformio.ini @@ -26,7 +26,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:crowpanel-esp32s3-4-epaper] extends = esp32s3_base @@ -56,7 +56,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:crowpanel-esp32s3-2-epaper] extends = esp32s3_base @@ -86,4 +86,4 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip diff --git a/variants/esp32s3/heltec_vision_master_e213/platformio.ini b/variants/esp32s3/heltec_vision_master_e213/platformio.ini index 4ace5a45a..1c4c69afe 100644 --- a/variants/esp32s3/heltec_vision_master_e213/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_e213/platformio.ini @@ -29,7 +29,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip upload_speed = 115200 [env:heltec-vision-master-e213-inkhud] diff --git a/variants/esp32s3/heltec_vision_master_e290/platformio.ini b/variants/esp32s3/heltec_vision_master_e290/platformio.ini index e86746b67..5affd24de 100644 --- a/variants/esp32s3/heltec_vision_master_e290/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_e290/platformio.ini @@ -32,7 +32,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip upload_speed = 115200 [env:heltec-vision-master-e290-inkhud] diff --git a/variants/esp32s3/heltec_wireless_paper/platformio.ini b/variants/esp32s3/heltec_wireless_paper/platformio.ini index 673c834ea..ce4bed30e 100644 --- a/variants/esp32s3/heltec_wireless_paper/platformio.ini +++ b/variants/esp32s3/heltec_wireless_paper/platformio.ini @@ -29,7 +29,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip upload_speed = 115200 [env:heltec-wireless-paper-inkhud] diff --git a/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini b/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini index 8543e414f..b34adfb17 100644 --- a/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini +++ b/variants/esp32s3/heltec_wireless_paper_v1/platformio.ini @@ -26,5 +26,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip upload_speed = 115200 diff --git a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini index 256cdc0d0..f9d1ea7db 100644 --- a/variants/esp32s3/tlora_t3s3_epaper/platformio.ini +++ b/variants/esp32s3/tlora_t3s3_epaper/platformio.ini @@ -31,7 +31,7 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:tlora-t3s3-epaper-inkhud] extends = esp32s3_base, inkhud diff --git a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini index a4687669b..2a6cea73e 100644 --- a/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini +++ b/variants/nrf52840/ELECROW-ThinkNode-M1/platformio.ini @@ -33,7 +33,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip # renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM khoih-prog/nRF52_PWM@1.0.1 ;upload_protocol = fs diff --git a/variants/nrf52840/heltec_mesh_pocket/platformio.ini b/variants/nrf52840/heltec_mesh_pocket/platformio.ini index 646304a5a..9fbcc890d 100644 --- a/variants/nrf52840/heltec_mesh_pocket/platformio.ini +++ b/variants/nrf52840/heltec_mesh_pocket/platformio.ini @@ -38,7 +38,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:heltec-mesh-pocket-5000-inkhud] extends = nrf52840_base, inkhud @@ -101,7 +101,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:heltec-mesh-pocket-10000-inkhud] extends = nrf52840_base, inkhud diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini index 69264f0df..a27c5d490 100644 --- a/variants/nrf52840/heltec_mesh_solar/platformio.ini +++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini @@ -68,7 +68,7 @@ build_flags = ${heltec_mesh_solar_base.build_flags} lib_deps = ${heltec_mesh_solar_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip [env:heltec-mesh-solar-inkhud] extends = heltec_mesh_solar_base, inkhud diff --git a/variants/nrf52840/meshlink/platformio.ini b/variants/nrf52840/meshlink/platformio.ini index e2631affe..28122d9bd 100644 --- a/variants/nrf52840/meshlink/platformio.ini +++ b/variants/nrf52840/meshlink/platformio.ini @@ -47,7 +47,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshlin lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini index 60d83b95a..d8fbaf8ff 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini @@ -34,7 +34,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_w lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip debug_tool = jlink [env:seeed_wio_tracker_L1_eink-inkhud] diff --git a/variants/nrf52840/t-echo-lite/platformio.ini b/variants/nrf52840/t-echo-lite/platformio.ini index c873dea37..1b725815e 100644 --- a/variants/nrf52840/t-echo-lite/platformio.ini +++ b/variants/nrf52840/t-echo-lite/platformio.ini @@ -30,5 +30,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo- lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip ;upload_protocol = fs diff --git a/variants/nrf52840/t-echo/platformio.ini b/variants/nrf52840/t-echo/platformio.ini index 4acd70b02..89ce488ad 100644 --- a/variants/nrf52840/t-echo/platformio.ini +++ b/variants/nrf52840/t-echo/platformio.ini @@ -30,7 +30,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo> lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master - https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib lewisxhe/SensorLib@0.3.4 ;upload_protocol = fs From 840b29860f4b8b36a3ba9c8d984217a1d7a18455 Mon Sep 17 00:00:00 2001 From: zeropt Date: Fri, 20 Feb 2026 04:12:22 -0800 Subject: [PATCH 176/223] skip header items when enabling the InkHUD menu cursor (#9552) * onNavUp() sets the menu cursor to the last item if not shown * skip headers when showing the menu cursor in onNavUp() and onNavDown() * skip headers when showing the menu cursor in onButtonShortPress() * brace cursor incrementing --------- Co-authored-by: Ben Meadors Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> --- .../InkHUD/Applets/System/Menu/MenuApplet.cpp | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index 26d6f03d3..520a6de97 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -1524,7 +1524,15 @@ void InkHUD::MenuApplet::onButtonShortPress() if (!settings->joystick.enabled) { if (!cursorShown) { cursorShown = true; + // Select the first item that isn't a header cursor = 0; + while (cursor < items.size() && items.at(cursor).isHeader) { + cursor++; + } + if (cursor >= items.size()) { + cursorShown = false; + cursor = 0; + } } else { do { cursor = (cursor + 1) % items.size(); @@ -1576,7 +1584,15 @@ void InkHUD::MenuApplet::onNavUp() if (!cursorShown) { cursorShown = true; - cursor = 0; + // Select the last item that isn't a header + cursor = items.size() - 1; + while (items.at(cursor).isHeader) { + if (cursor == 0) { + cursorShown = false; + break; + } + cursor--; + } } else { do { if (cursor == 0) @@ -1597,7 +1613,15 @@ void InkHUD::MenuApplet::onNavDown() if (!cursorShown) { cursorShown = true; + // Select the first item that isn't a header cursor = 0; + while (cursor < items.size() && items.at(cursor).isHeader) { + cursor++; + } + if (cursor >= items.size()) { + cursorShown = false; + cursor = 0; + } } else { do { cursor = (cursor + 1) % items.size(); From 6bc3e3153787252ad2dc0564484f6058a8bf7940 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 06:45:46 -0600 Subject: [PATCH 177/223] Upgrade trunk (#9696) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 77b256a18..f33d7c416 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,15 +9,15 @@ plugins: lint: enabled: - checkov@3.2.504 - - renovate@43.25.8 + - renovate@43.26.4 - prettier@3.8.1 - - trufflehog@3.93.3 + - trufflehog@3.93.4 - yamllint@1.38.0 - bandit@1.9.3 - trivy@0.69.1 - taplo@0.10.0 - - ruff@0.15.1 - - isort@7.0.0 + - ruff@0.15.2 + - isort@8.0.0 - markdownlint@0.47.0 - oxipng@10.1.0 - svgo@4.0.0 From 8f81b194d3356ba2dccdca0b3151a3c17e9d40ee Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:38:07 -0500 Subject: [PATCH 178/223] BLE Pairing fix (#9701) * BLE Pairing fix * gating for consistency --------- Co-authored-by: Ben Meadors --- src/graphics/Screen.cpp | 4 ++++ src/nimble/NimbleBluetooth.cpp | 23 ++++++++++++++++------- src/platform/nrf52/NRF52Bluetooth.cpp | 16 +++++++++++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 111a47f7c..c76019fd3 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -875,6 +875,10 @@ int32_t Screen::runOnce() break; case Cmd::STOP_ALERT_FRAME: NotificationRenderer::pauseBanner = false; + // Return from one-off alert mode back to regular frames. + if (!showingNormalScreen && NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) { + setFrames(); + } break; case Cmd::STOP_BOOT_SCREEN: EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 89d74dbd7..3bb4ce817 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -52,6 +52,20 @@ NimBLEServer *bleServer; static bool passkeyShowing; static std::atomic nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection" +static void clearPairingDisplay() +{ + if (!passkeyShowing) { + return; + } + + passkeyShowing = false; +#if HAS_SCREEN + if (screen) { + screen->endAlert(); + } +#endif +} + class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread { /* @@ -630,13 +644,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); bluetoothStatus->updateStatus(&newStatus); - - // Todo: migrate this display code back into Screen class, and observe bluetoothStatus - if (passkeyShowing) { - passkeyShowing = false; - if (screen) - screen->endAlert(); - } + clearPairingDisplay(); // Store the connection handle for future use #ifdef NIMBLE_TWO @@ -693,6 +701,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); + clearPairingDisplay(); if (bluetoothPhoneAPI) { bluetoothPhoneAPI->close(); diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 6a552f236..307e35b0c 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -32,6 +32,7 @@ static uint8_t toRadioBytes[meshtastic_ToRadio_size]; static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; static uint16_t connectionHandle; +static bool passkeyShowing; class BluetoothPhoneAPI : public PhoneAPI { @@ -86,6 +87,16 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason) // Notify UI (or any other interested firmware components) meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); bluetoothStatus->updateStatus(&newStatus); + +#if HAS_SCREEN + // If a pairing prompt is active, make sure we dismiss it on disconnect/cancel/failure paths. + if (passkeyShowing) { + passkeyShowing = false; + if (screen) { + screen->endAlert(); + } + } +#endif } void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { @@ -400,6 +411,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke }); } #endif + passkeyShowing = true; + if (match_request) { uint32_t start_time = millis(); while (millis() < start_time + 30000) { @@ -451,6 +464,7 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu } // Todo: migrate this display code back into Screen class, and observe bluetoothStatus + passkeyShowing = false; if (screen) { screen->endAlert(); } @@ -464,4 +478,4 @@ void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length) logRadio.indicate(logMessage, (uint16_t)length); else logRadio.notify(logMessage, (uint16_t)length); -} \ No newline at end of file +} From 299ef95f259dd85096a0d1d111bafb9460125002 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 20 Feb 2026 11:25:28 -0600 Subject: [PATCH 179/223] Revert "Add agc reset attempt (#8163)" (#9702) This reverts commit ac611c4b6262ae54336317c9e4167827ba6edd78. --- src/main.cpp | 9 --------- src/main.h | 1 - src/mesh/RadioLibInterface.cpp | 2 -- 3 files changed, 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f70b3975e..b36a93ed4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,7 +7,6 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "PowerMon.h" -#include "RadioLibInterface.h" #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" @@ -192,8 +191,6 @@ bool kb_found = false; // global bool to record that on-screen keyboard (OSK) is present bool osk_found = false; -unsigned long last_listen = 0; - // The I2C address of the RTC Module (if found) ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE; // The I2C address of the Accelerometer (if found) @@ -1121,12 +1118,6 @@ void loop() #endif power->powerCommandsCheck(); - if (RadioLibInterface::instance != nullptr && !Throttle::isWithinTimespanMs(last_listen, 1000 * 60) && - !(RadioLibInterface::instance->isSending() || RadioLibInterface::instance->isActivelyReceiving())) { - RadioLibInterface::instance->startReceive(); - LOG_DEBUG("attempting AGC reset"); - } - #ifdef DEBUG_STACK static uint32_t lastPrint = 0; if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) { diff --git a/src/main.h b/src/main.h index 619eb184e..91e27951f 100644 --- a/src/main.h +++ b/src/main.h @@ -33,7 +33,6 @@ extern ScanI2C::DeviceAddress cardkb_found; extern uint8_t kb_model; extern bool kb_found; extern bool osk_found; -extern unsigned long last_listen; extern ScanI2C::DeviceAddress rtc_found; extern ScanI2C::DeviceAddress accelerometer_found; extern ScanI2C::FoundDevice rgb_found; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index af6ab30c1..61d93c912 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -517,8 +517,6 @@ void RadioLibInterface::handleReceiveInterrupt() void RadioLibInterface::startReceive() { - // Note the updated timestamp, to avoid unneeded AGC resets - last_listen = millis(); isReceiving = true; powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); } From 8c37d6923b99eb42f59cef6e8688fe9232bb67df Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 20 Feb 2026 12:00:30 -0600 Subject: [PATCH 180/223] Add #include "RadioLibInterface.h" --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index b36a93ed4..a00fb58de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include "NodeDB.h" #include "PowerFSM.h" #include "PowerMon.h" +#include "RadioLibInterface.h" #include "ReliableRouter.h" #include "airtime.h" #include "buzz.h" From af518fbd2b256eaebb5b23841e3ca9d8892dd791 Mon Sep 17 00:00:00 2001 From: Wessel Date: Fri, 20 Feb 2026 23:12:54 +0100 Subject: [PATCH 181/223] Hold GC1109 FEM power during deep sleep for LNA RX wake (#9572) * Hold GC1109 PA_POWER and PA_EN during deep sleep for LNA RX wake Use rtc_gpio_hold_en to latch PA_POWER (LDO) and PA_EN (CSD) HIGH during deep sleep so the GC1109 LNA remains powered for wake-on-packet RX. Previously these pins used weak pull-ups which could lose state. On deep sleep wake, skip these pins in the blanket RTC hold release and instead release them in SX126xInterface::init() after GPIO registers are set HIGH first, avoiding a power glitch on the GC1109. Trade-off: ~6.5mA additional deep sleep current for significantly improved wake-on-packet RX sensitivity (~17dB). Reference: https://github.com/meshcore-dev/MeshCore/pull/1600 * Add LDO startup delay before GC1109 chip enable TLV75733P LDO has ~550us startup time (datasheet tSTR). On cold boot, wait 1ms for VBAT to stabilise before driving CSD/CPS, per GC1109 power-on sequence requirement. On deep sleep wake the LDO is held on via RTC latch so no delay is needed. --- src/mesh/SX126xInterface.cpp | 23 +++++++++++++++++++++++ src/sleep.cpp | 16 ++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 9dfc46bee..08ee2ff48 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -6,6 +6,10 @@ #ifdef ARCH_PORTDUINO #include "PortduinoGlue.h" #endif +#if defined(USE_GC1109_PA) && defined(ARCH_ESP32) +#include +#include +#endif #include "Throttle.h" @@ -55,14 +59,33 @@ template bool SX126xInterface::init() #if defined(USE_GC1109_PA) // GC1109 FEM chip initialization // See variant.h for full pin mapping and control logic documentation + // + // On deep sleep wake, PA_POWER and PA_EN are held HIGH by RTC latch (set in + // enableLoraInterrupt). We configure GPIO registers before releasing the hold + // so the pad transitions atomically from held-HIGH to register-HIGH with no + // power glitch. On cold boot the hold_dis is a harmless no-op. // VFEM_Ctrl (LORA_PA_POWER): Power enable for GC1109 LDO (always on) pinMode(LORA_PA_POWER, OUTPUT); digitalWrite(LORA_PA_POWER, HIGH); + rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER); + + // TLV75733P LDO has ~550us startup time (datasheet tSTR). On cold boot, wait + // for VBAT to stabilise before driving CSD/CPS, per GC1109 requirement: + // "VBAT must be prior to CSD/CPS/CTX for the power on sequence" + // On deep sleep wake the LDO was held on via RTC latch, so no delay needed. +#if defined(ARCH_ESP32) + if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED) { + delayMicroseconds(1000); + } +#else + delayMicroseconds(1000); +#endif // CSD (LORA_PA_EN): Chip enable - must be HIGH to enable GC1109 for both RX and TX pinMode(LORA_PA_EN, OUTPUT); digitalWrite(LORA_PA_EN, HIGH); + rtc_gpio_hold_dis((gpio_num_t)LORA_PA_EN); // CPS (LORA_PA_TX_EN): PA mode select - HIGH enables full PA during TX, LOW for RX (don't care) // Note: TX/RX path switching (CTX) is handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH diff --git a/src/sleep.cpp b/src/sleep.cpp index 7c768d573..d42b9841a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -162,6 +162,13 @@ void initDeepSleep() if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) { LOG_DEBUG("Disable any holds on RTC IO pads"); for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { +#if defined(USE_GC1109_PA) + // Skip GC1109 FEM power pins - they are held HIGH during deep sleep to keep + // the LNA active for RX wake. Released later in SX126xInterface::init() after + // GPIO registers are set HIGH first, avoiding a power glitch. + if (i == LORA_PA_POWER || i == LORA_PA_EN) + continue; +#endif if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) rtc_gpio_hold_dis((gpio_num_t)i); @@ -556,8 +563,13 @@ void enableLoraInterrupt() #endif #if defined(USE_GC1109_PA) - gpio_pullup_en((gpio_num_t)LORA_PA_POWER); - gpio_pullup_en((gpio_num_t)LORA_PA_EN); + // Keep GC1109 FEM powered during deep sleep so LNA remains active for RX wake. + // Set PA_POWER and PA_EN HIGH (overrides SX126xInterface::sleep() shutdown), + // then latch with RTC hold so the state survives deep sleep. + digitalWrite(LORA_PA_POWER, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER); + digitalWrite(LORA_PA_EN, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_PA_EN); gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN); #endif From a5523b04ef30bb5c62a56abd2f49c5a592b628ad Mon Sep 17 00:00:00 2001 From: David Remba Date: Sat, 21 Feb 2026 03:15:22 -0500 Subject: [PATCH 182/223] Fix/rak3401 button (#9668) --- variants/nrf52840/rak3401_1watt/variant.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index 80b09cf69..9f81a15ba 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -52,6 +52,12 @@ extern "C" { #define LED_STATE_ON 1 // State when LED is litted +/* + * Buttons + */ +#define PIN_BUTTON1 (9) +#define BUTTON_NEED_PULLUP + /* * Analog pins */ From 8feb34e7a8f68f13fedc3a1fd9fc4bcc70a31990 Mon Sep 17 00:00:00 2001 From: Mike Robbins Date: Sat, 21 Feb 2026 07:14:04 -0500 Subject: [PATCH 183/223] RadioLib edge-triggered interrupts robustness (#9658) * Fix potential race condition (read, ISR write, clear) in NotifiedWorkerThread * Check for missed edge-triggered interrupts race condition between startReceive and enableInterrupt * Occasionally poll to catch missed RX_DONE interrupts. (RadioLibInterface::pollMissedIrqs) * Simplify RadioLibInterface::checkRxDoneIrqFlag() --------- Co-authored-by: Ben Meadors --- src/concurrency/NotifiedWorkerThread.cpp | 6 ++++-- src/concurrency/NotifiedWorkerThread.h | 3 ++- src/main.cpp | 8 ++++++++ src/mesh/LR11x0Interface.cpp | 1 + src/mesh/RF95Interface.cpp | 1 + src/mesh/RadioLibInterface.cpp | 16 ++++++++++++++++ src/mesh/RadioLibInterface.h | 7 +++++++ src/mesh/SX126xInterface.cpp | 1 + src/mesh/SX128xInterface.cpp | 1 + 9 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/concurrency/NotifiedWorkerThread.cpp b/src/concurrency/NotifiedWorkerThread.cpp index 0e4e31d9b..29aff32a5 100644 --- a/src/concurrency/NotifiedWorkerThread.cpp +++ b/src/concurrency/NotifiedWorkerThread.cpp @@ -76,8 +76,10 @@ bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrit void NotifiedWorkerThread::checkNotification() { - auto n = notification; - notification = 0; // clear notification + // Atomically read and clear. (This avoids a potential race condition where an interrupt handler could set a new notification + // after checkNotification reads but before it clears, which would cause us to miss that notification until the next one comes + // in.) + auto n = notification.exchange(0); // read+clear atomically: like `n = notification; notification = 0;` but interrupt-safe if (n) { onNotify(n); } diff --git a/src/concurrency/NotifiedWorkerThread.h b/src/concurrency/NotifiedWorkerThread.h index 7a150b0b0..166b9ea65 100644 --- a/src/concurrency/NotifiedWorkerThread.h +++ b/src/concurrency/NotifiedWorkerThread.h @@ -1,6 +1,7 @@ #pragma once #include "OSThread.h" +#include namespace concurrency { @@ -13,7 +14,7 @@ class NotifiedWorkerThread : public OSThread /** * The notification that was most recently used to wake the thread. Read from runOnce() */ - uint32_t notification = 0; + std::atomic notification{0}; public: NotifiedWorkerThread(const char *name) : OSThread(name) {} diff --git a/src/main.cpp b/src/main.cpp index a00fb58de..b4c1f6519 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1119,6 +1119,14 @@ void loop() #endif power->powerCommandsCheck(); + if (RadioLibInterface::instance != nullptr) { + static uint32_t lastRadioMissedIrqPoll; + if (!Throttle::isWithinTimespanMs(lastRadioMissedIrqPoll, 1000)) { + lastRadioMissedIrqPoll = millis(); + RadioLibInterface::instance->pollMissedIrqs(); + } + } + #ifdef DEBUG_STACK static uint32_t lastPrint = 0; if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) { diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index 7c73b56cd..a6ac4f418 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -263,6 +263,7 @@ template void LR11x0Interface::startReceive() // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); + checkRxDoneIrqFlag(); #endif } diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 0c12401ca..b3aa72f7a 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -301,6 +301,7 @@ void RF95Interface::startReceive() // Must be done AFTER, starting receive, because startReceive clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); + checkRxDoneIrqFlag(); } bool RF95Interface::isChannelActive() diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 61d93c912..78e0fc5b4 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -521,6 +521,22 @@ void RadioLibInterface::startReceive() powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn); } +void RadioLibInterface::pollMissedIrqs() +{ + // RadioLibInterface::enableInterrupt uses EDGE-TRIGGERED interrupts. Poll as a backup to catch missed edges. + if (isReceiving) { + checkRxDoneIrqFlag(); + } +} + +void RadioLibInterface::checkRxDoneIrqFlag() +{ + if (iface->checkIrq(RADIOLIB_IRQ_RX_DONE)) { + LOG_WARN("caught missed RX_DONE"); + notify(ISR_RX, true); + } +} + void RadioLibInterface::configHardwareForSend() { powerMon->setState(meshtastic_PowerMon_State_Lora_TXOn); diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 833c88710..4bca99b5f 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -112,6 +112,11 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ virtual void enableInterrupt(void (*)()) = 0; + /** + * Poll as a backup to catch missed edge-triggered interrupts. + */ + void pollMissedIrqs(); + /** * Debugging counts */ @@ -264,4 +269,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override; + + void checkRxDoneIrqFlag(); }; diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 08ee2ff48..d6f1ac408 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -351,6 +351,7 @@ template void SX126xInterface::startReceive() // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); + checkRxDoneIrqFlag(); #endif } diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 9fcedfe49..0e882ef05 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -271,6 +271,7 @@ template void SX128xInterface::startReceive() // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); + checkRxDoneIrqFlag(); #endif } From f615990c0067a4ca5156ed08ab80bfe53ccf3da0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 21 Feb 2026 09:06:17 -0600 Subject: [PATCH 184/223] Limit http connections and add free heap check before allocating for SSL (#9693) * Reduce maximum concurrent HTTPS connections to save memory * Add heap check and limit connections to prevent HTTPS connection related crashes * Use Throttle::isWithinTimespanMs for overflow-safe heap warning throttle * Update src/mesh/http/WebServer.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix double-free heap corruption and parser leak in web server handlers - Remove double-free in handleFsBrowseStatic, handleNodes, handleScanNetworks: JSONValue(const JSONArray&) shallow-copies pointers, so delete value already recursively frees all elements. The explicit cleanup loops were deleting the same pointers a second time, corrupting the ESP32 heap allocator metadata. This is the likely root cause of #8827 (SSL setup failures after uptime). - Fix memory leak in handleFormUpload: two early-return paths were missing delete parser. - Remove unused global HTTPClient httpClient and its includes. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mesh/http/ContentHandler.cpp | 21 ++------------------- src/mesh/http/WebServer.cpp | 25 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 281ece464..6b3aa4859 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -46,10 +46,6 @@ using namespace httpsserver; #include "mesh/http/ContentHandler.h" -#include -#include -HTTPClient httpClient; - #define DEST_FS_USES_LITTLEFS // We need to specify some content-type mapping, so the resources get delivered with the @@ -344,11 +340,6 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) res->print(jsonString.c_str()); delete value; - - // Clean up the fileList to prevent memory leak - for (auto *val : fileList) { - delete val; - } } void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) @@ -543,6 +534,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res) if (name != "file") { LOG_DEBUG("Skip unexpected field"); res->println("

No file found.

"); + delete parser; return; } @@ -550,6 +542,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res) if (filename == "") { LOG_DEBUG("Skip unexpected field"); res->println("

No file found.

"); + delete parser; return; } @@ -783,11 +776,6 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res) std::string jsonString = value->Stringify(); res->print(jsonString.c_str()); delete value; - - // Clean up the nodesArray to prevent memory leak - for (auto *val : nodesArray) { - delete val; - } } /* @@ -941,10 +929,5 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res) std::string jsonString = value->Stringify(); res->print(jsonString.c_str()); delete value; - - // Clean up the networkObjs to prevent memory leak - for (auto *val : networkObjs) { - delete val; - } } #endif \ No newline at end of file diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 3a264fa5a..90fd8b084 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -55,6 +56,12 @@ static const int32_t ACTIVE_INTERVAL_MS = 50; static const int32_t MEDIUM_INTERVAL_MS = 200; static const int32_t IDLE_INTERVAL_MS = 1000; +// Maximum concurrent HTTPS connections (reduced from default 4 to save memory) +static const uint8_t MAX_HTTPS_CONNECTIONS = 2; + +// Minimum free heap required for SSL handshake (~40KB for mbedTLS contexts) +static const uint32_t MIN_HEAP_FOR_SSL = 40000; + static SSLCert *cert; static HTTPSServer *secureServer; static HTTPServer *insecureServer; @@ -67,8 +74,20 @@ static void handleWebResponse() if (isWifiAvailable()) { if (isWebServerReady) { - if (secureServer) - secureServer->loop(); + // Check heap before HTTPS processing - SSL requires significant memory + if (secureServer) { + uint32_t freeHeap = ESP.getFreeHeap(); + if (freeHeap >= MIN_HEAP_FOR_SSL) { + secureServer->loop(); + } else { + // Skip HTTPS when memory is low to prevent SSL setup failures + static uint32_t lastHeapWarning = 0; + if (lastHeapWarning == 0 || !Throttle::isWithinTimespanMs(lastHeapWarning, 30000)) { + LOG_WARN("Low heap (%u bytes), skipping HTTPS processing", freeHeap); + lastHeapWarning = millis(); + } + } + } insecureServer->loop(); } } @@ -229,7 +248,7 @@ void initWebServer() LOG_DEBUG("Init Web Server"); // We can now use the new certificate to setup our server as usual. - secureServer = new HTTPSServer(cert); + secureServer = new HTTPSServer(cert, 443, MAX_HTTPS_CONNECTIONS); insecureServer = new HTTPServer(); registerHandlers(insecureServer, secureServer); From 9383d0bcfc74c77ea8ecbc8a633155cdcc2bfc69 Mon Sep 17 00:00:00 2001 From: Wessel Date: Sat, 21 Feb 2026 16:08:14 +0100 Subject: [PATCH 185/223] Apply SX1262 register 0x8B5 patch for improved GC1109 RX sensitivity (#9571) * Apply SX1262 register 0x8B5 patch for improved GC1109 RX sensitivity Sets the LSB of undocumented SX1262 register 0x8B5 on Heltec V4 and Wireless Tracker V2 boards with the GC1109 FEM. This patch was recommended by Heltec/Semtech and tested in MeshCore PR #1398, where it significantly reduced packet loss on the Heltec V4. * Use higher level function * Add .venv/ to .gitignore --- .gitignore | 1 + src/mesh/SX126xInterface.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/.gitignore b/.gitignore index d6d97c6c4..43cee78db 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ __pycache__ *~ venv/ +.venv/ release/ .vscode/extensions.json /compile_commands.json diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index d6f1ac408..5b2fb3ac3 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -194,6 +194,17 @@ template bool SX126xInterface::init() LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", result); } +#ifdef USE_GC1109_PA + // Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity + // on boards with the GC1109 FEM. Sets bit 0 of register 0x8B5. + // Reference: https://github.com/meshcore-dev/MeshCore/pull/1398 + if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) == RADIOLIB_ERR_NONE) { + LOG_INFO("Applied SX1262 register 0x8B5 patch for GC1109 RX improvement"); + } else { + LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for GC1109"); + } +#endif + #if 0 // Read/write a register we are not using (only used for FSK mode) to test SPI comms uint8_t crcLSB = 0; From 0726a9f7741f00c2979729080d6919920ff0cdfd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:46:39 +0100 Subject: [PATCH 186/223] chore(deps): update radiolib to v7.6.0 (#9695) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index ccc5c9755..e488d1e9c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -117,7 +117,7 @@ lib_deps = [radiolib_base] lib_deps = # renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib - jgromes/RadioLib@7.5.0 + jgromes/RadioLib@7.6.0 [device-ui_base] lib_deps = From 7df37f8254bbc348f9ddfc489cbfcbefc5bc89dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:46:39 +0100 Subject: [PATCH 187/223] chore(deps): update radiolib to v7.6.0 (#9695) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index ccc5c9755..e488d1e9c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -117,7 +117,7 @@ lib_deps = [radiolib_base] lib_deps = # renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib - jgromes/RadioLib@7.5.0 + jgromes/RadioLib@7.6.0 [device-ui_base] lib_deps = From 417ca86c30b099483d6caa2c2620f4448f6a94a1 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Mon, 23 Feb 2026 04:07:32 +0100 Subject: [PATCH 188/223] Split module includes for AQ module (#9711) --- src/modules/Modules.cpp | 6 +++++- src/modules/Telemetry/Sensor/TelemetrySensor.h | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 0708afe82..64e90c9c2 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -56,11 +56,15 @@ #endif #if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "main.h" -#include "modules/Telemetry/AirQualityTelemetry.h" #include "modules/Telemetry/EnvironmentTelemetry.h" #include "modules/Telemetry/HealthTelemetry.h" #include "modules/Telemetry/Sensor/TelemetrySensor.h" #endif +#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR +#include "main.h" +#include "modules/Telemetry/AirQualityTelemetry.h" +#include "modules/Telemetry/Sensor/TelemetrySensor.h" +#endif #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY #include "modules/Telemetry/PowerTelemetry.h" #endif diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h index 6c53bbd72..47deaa936 100644 --- a/src/modules/Telemetry/Sensor/TelemetrySensor.h +++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h @@ -1,6 +1,6 @@ #include "configuration.h" -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" From 4f84cf011d62d029b0dd692178d902bccbf09ee7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 05:38:33 -0600 Subject: [PATCH 189/223] Upgrade trunk (#9724) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index f33d7c416..00a2da300 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,8 +8,8 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.504 - - renovate@43.26.4 + - checkov@3.2.505 + - renovate@43.31.1 - prettier@3.8.1 - trufflehog@3.93.4 - yamllint@1.38.0 From 99e88bb499fd7d3bc0e566eaae06516ffdc5a699 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:08:24 +0100 Subject: [PATCH 190/223] Support mini ePaper S3 Kit (#9335) * initial draft * update DC pin * add ADC channel * fix e-ink display * update GxEPD2 reference * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- boards/mini-epaper-s3.json | 40 +++++++++++++ src/graphics/EInkDisplay2.cpp | 11 ++++ src/graphics/EInkDisplay2.h | 3 +- .../esp32s3/mini-epaper-s3/pins_arduino.h | 26 +++++++++ .../esp32s3/mini-epaper-s3/platformio.ini | 31 ++++++++++ variants/esp32s3/mini-epaper-s3/variant.h | 57 +++++++++++++++++++ 6 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 boards/mini-epaper-s3.json create mode 100644 variants/esp32s3/mini-epaper-s3/pins_arduino.h create mode 100644 variants/esp32s3/mini-epaper-s3/platformio.ini create mode 100644 variants/esp32s3/mini-epaper-s3/variant.h diff --git a/boards/mini-epaper-s3.json b/boards/mini-epaper-s3.json new file mode 100644 index 000000000..5140f88be --- /dev/null +++ b/boards/mini-epaper-s3.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_ESP32S3_DEV", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": ["wifi"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "LilyGo Mini-Epaper-S3 (4 MB Flash, 2MB PSRAM)", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://www.lilygo.cc", + "vendor": "LilyGo" +} diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 1678da793..c05864349 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -259,6 +259,17 @@ bool EInkDisplay::connect() adafruitDisplay->setRotation(3); adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } +#elif defined(MINI_EPAPER_S3) + spi1 = new SPIClass(HSPI); + spi1->begin(PIN_SPI1_SCK, PIN_SPI1_MISO, PIN_SPI1_MOSI, PIN_EINK_CS); + + // Create GxEPD2 objects + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1); + adafruitDisplay = new GxEPD2_BW(*lowLevel); + + // Init GxEPD2 + adafruitDisplay->init(); + adafruitDisplay->setRotation(1); #elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) // Detect display model, before starting SPI diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index f5418b069..14adeda12 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -93,7 +93,8 @@ class EInkDisplay : public OLEDDisplay SPIClass *hspi = NULL; #endif -#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) +#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) || \ + defined(MINI_EPAPER_S3) SPIClass *spi1 = NULL; #endif diff --git a/variants/esp32s3/mini-epaper-s3/pins_arduino.h b/variants/esp32s3/mini-epaper-s3/pins_arduino.h new file mode 100644 index 000000000..a4b3c4bf7 --- /dev/null +++ b/variants/esp32s3/mini-epaper-s3/pins_arduino.h @@ -0,0 +1,26 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SDA = 18; +static const uint8_t SCL = 9; + +// Default SPI will be mapped to Radio +static const uint8_t SS = -1; +static const uint8_t MOSI = 17; +static const uint8_t MISO = 6; +static const uint8_t SCK = 8; + +#define SPI_MOSI (39) +#define SPI_SCK (41) +#define SPI_MISO (38) +#define SPI_CS (40) + +#define SDCARD_CS SPI_CS + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/mini-epaper-s3/platformio.ini b/variants/esp32s3/mini-epaper-s3/platformio.ini new file mode 100644 index 000000000..eb68e9d05 --- /dev/null +++ b/variants/esp32s3/mini-epaper-s3/platformio.ini @@ -0,0 +1,31 @@ +[env:mini-epaper-s3] +;custom_meshtastic_hw_model = +custom_meshtastic_hw_model_slug = MINI_EPAPER_S3 +custom_meshtastic_architecture = esp32-s3 +custom_meshtastic_actively_supported = true +custom_meshtastic_support_level = 1 +custom_meshtastic_display_name = LILYGO Mini ePaper S3 E-Ink +custom_meshtastic_images = mini-epaper-s3.svg +custom_meshtastic_tags = LilyGo +custom_meshtastic_requires_dfu = no + +extends = esp32s3_base +board = mini-epaper-s3 +board_check = true +upload_protocol = esptool + +build_flags = + ${esp32s3_base.build_flags} + -I variants/esp32s3/mini-epaper-s3 + -DMINI_EPAPER_S3 + -DUSE_EINK + -DEINK_DISPLAY_MODEL=GxEPD2_102 + -DEINK_WIDTH=128 + -DEINK_HEIGHT=80 + +lib_deps = + ${esp32s3_base.lib_deps} + # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master + https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip + # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib + lewisxhe/SensorLib@0.3.3 diff --git a/variants/esp32s3/mini-epaper-s3/variant.h b/variants/esp32s3/mini-epaper-s3/variant.h new file mode 100644 index 000000000..b464c9b4a --- /dev/null +++ b/variants/esp32s3/mini-epaper-s3/variant.h @@ -0,0 +1,57 @@ +// Display (E-Ink) + +#define PIN_EINK_CS 13 +#define PIN_EINK_BUSY 10 +#define PIN_EINK_RES 11 +#define PIN_EINK_SCLK 14 +#define PIN_EINK_MOSI 15 +#define PIN_EINK_DC 12 +#define PIN_EINK_EN 42 + +#define SPI_INTERFACES_COUNT 2 +#define PIN_SPI1_MISO -1 +#define PIN_SPI1_MOSI PIN_EINK_MOSI +#define PIN_SPI1_SCK PIN_EINK_SCLK +#define DISPLAY_FORCE_SMALL_FONTS + +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define BATTERY_PIN 2 // A battery voltage measurement pin, voltage divider connected here to +// measure battery voltage ratio of voltage divider = 2.0 (assumption) +#define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. +#define ADC_CHANNEL ADC1_GPIO2_CHANNEL + +#define HAS_GPS 0 +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +#define BUTTON_PIN 3 +#define BUTTON_NEED_PULLUP +#define ALT_BUTTON_PIN 4 +#define ALT_BUTTON_ACTIVE_LOW true +#define ALT_BUTTON_ACTIVE_PULLUP true +#define PIN_BUTTON3 0 + +// #define HAS_SDCARD 1 +// #define SDCARD_USE_SOFT_SPI + +// PCF85063 RTC Module +#define PCF85063_RTC 0x51 +#define HAS_RTC 1 + +#define USE_SX1262 +#define LORA_DIO1 5 +#define LORA_SCK 8 +#define LORA_MISO 6 +#define LORA_MOSI 17 +#define LORA_CS 7 // CS not connected; IO7 is free +#define LORA_RESET 21 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 16 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif From 607b631114349234b8859c2da0d0f553b3d344f3 Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 23 Feb 2026 21:42:41 -0500 Subject: [PATCH 191/223] meshtasticd: Add Luckfox Lyra Hat pinmaps (#9730) Luckfox Lyra Plus + Waveshare Pi Pico Hat Luckfox Lyra Ultra + wehooper4's "Luckfox Ultra" hat. --- bin/config.d/lora-lyra-ultra_1w.yaml | 16 ++++++++++++ bin/config.d/lora-lyra-ultra_2w.yaml | 17 +++++++++++++ .../lora-lyra-ws-raspberry-pi-pico-hat.yaml | 25 +++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 bin/config.d/lora-lyra-ultra_1w.yaml create mode 100644 bin/config.d/lora-lyra-ultra_2w.yaml create mode 100644 bin/config.d/lora-lyra-ws-raspberry-pi-pico-hat.yaml diff --git a/bin/config.d/lora-lyra-ultra_1w.yaml b/bin/config.d/lora-lyra-ultra_1w.yaml new file mode 100644 index 000000000..0bdc05fef --- /dev/null +++ b/bin/config.d/lora-lyra-ultra_1w.yaml @@ -0,0 +1,16 @@ +# For use with Armbian luckfox-lyra-ultra-w +# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config +# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat +# 1 Watt Lyra Ultra hat +Lora: + Module: sx1262 + DIO2_AS_RF_SWITCH: true + DIO3_TCXO_VOLTAGE: true + CS: 10 + IRQ: 5 + Busy: 11 + Reset: 9 + RXen: 14 + + spidev: spidev0.0 #pins are (CS=10, CLK=8, MOSI=6, MISO=7) + spiSpeed: 2000000 diff --git a/bin/config.d/lora-lyra-ultra_2w.yaml b/bin/config.d/lora-lyra-ultra_2w.yaml new file mode 100644 index 000000000..a1fe6d7f7 --- /dev/null +++ b/bin/config.d/lora-lyra-ultra_2w.yaml @@ -0,0 +1,17 @@ +# For use with Armbian luckfox-lyra-ultra-w +# Enable overlay 'luckfox-lyra-ultra-w-spi0-cs0-spidev' with armbian-config +# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/Luckfox%20Ultra%20Hat +# 2 Watt Lyra Ultra hat +Lora: + Module: sx1262 + DIO2_AS_RF_SWITCH: true + DIO3_TCXO_VOLTAGE: true + SX126X_MAX_POWER: 8 + CS: 10 + IRQ: 5 + Busy: 11 + Reset: 9 + RXen: 14 + + spidev: spidev0.0 #pins are (CS=10, CLK=8, MOSI=6, MISO=7) + spiSpeed: 2000000 diff --git a/bin/config.d/lora-lyra-ws-raspberry-pi-pico-hat.yaml b/bin/config.d/lora-lyra-ws-raspberry-pi-pico-hat.yaml new file mode 100644 index 000000000..8425fc385 --- /dev/null +++ b/bin/config.d/lora-lyra-ws-raspberry-pi-pico-hat.yaml @@ -0,0 +1,25 @@ +# For use with Armbian luckfox-lyra // luckfox-lyra-plus +# Enable overlay 'luckfox-lyra-plus-spi0-cs0_rmio13-spidev' with armbian-config +# Waveshare LoRa HAT for Raspberry Pi Pico +# https://www.waveshare.com/wiki/Pico-LoRa-SX1262 +Lora: + Module: sx1262 + DIO2_AS_RF_SWITCH: true + DIO3_TCXO_VOLTAGE: true + spidev: spidev0.0 + CS: # GPIO0_B5 + pin: 13 + gpiochip: 0 + line: 13 + IRQ: # GPIO1_C2 + pin: 50 + gpiochip: 1 + line: 18 + Busy: # GPIO0_B4 + pin: 12 + gpiochip: 0 + line: 12 + Reset: # GPIO0_A2 + pin: 2 + gpiochip: 0 + line: 2 From 13b25c061429c4a40d5b9ebbf63de2d364f88e17 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:42:52 -0600 Subject: [PATCH 192/223] chore(deps): update meshtastic-st7789 digest to 9ee76d6 (#9729) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32s3/heltec_vision_master_t190/platformio.ini | 2 +- variants/nrf52840/heltec_mesh_node_t114/platformio.ini | 2 +- variants/nrf52840/heltec_mesh_solar/platformio.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/esp32s3/heltec_vision_master_t190/platformio.ini b/variants/esp32s3/heltec_vision_master_t190/platformio.ini index bbc518b39..44e8b2f2d 100644 --- a/variants/esp32s3/heltec_vision_master_t190/platformio.ini +++ b/variants/esp32s3/heltec_vision_master_t190/platformio.ini @@ -20,5 +20,5 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main - https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip + https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip upload_speed = 921600 diff --git a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini index a39872205..7cbc5f6a9 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/platformio.ini +++ b/variants/nrf52840/heltec_mesh_node_t114/platformio.ini @@ -23,4 +23,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_ lib_deps = ${nrf52840_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main - https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip + https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini index a27c5d490..b4964a077 100644 --- a/variants/nrf52840/heltec_mesh_solar/platformio.ini +++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini @@ -132,4 +132,4 @@ build_flags = ${heltec_mesh_solar_base.build_flags} lib_deps = ${heltec_mesh_solar_base.lib_deps} # renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main - https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip + https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip From f9d1f2414a91f3b2cb699dcecf7bfc2be085132f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 05:46:27 -0600 Subject: [PATCH 193/223] chore(deps): update sensorlib to v0.3.4 (#9727) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/esp32s3/mini-epaper-s3/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/esp32s3/mini-epaper-s3/platformio.ini b/variants/esp32s3/mini-epaper-s3/platformio.ini index eb68e9d05..f49be707f 100644 --- a/variants/esp32s3/mini-epaper-s3/platformio.ini +++ b/variants/esp32s3/mini-epaper-s3/platformio.ini @@ -28,4 +28,4 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib - lewisxhe/SensorLib@0.3.3 + lewisxhe/SensorLib@0.3.4 From cf998f03b62ad7477cef84ade25789b4f986d9aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 05:47:18 -0600 Subject: [PATCH 194/223] Upgrade trunk (#9731) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 00a2da300..a1d07145a 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,8 +8,8 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.505 - - renovate@43.31.1 + - checkov@3.2.506 + - renovate@43.31.9 - prettier@3.8.1 - trufflehog@3.93.4 - yamllint@1.38.0 From 9c439f8d0ab92e79b942e2dcdd1f8fd4865a5834 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Feb 2026 06:01:52 -0600 Subject: [PATCH 195/223] FIx loophole with telemetry coercion --- src/mesh/NodeDB.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index a52fa4478..50f43b2c4 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -322,9 +322,9 @@ NodeDB::NodeDB() // Uncomment below to always enable UDP broadcasts // config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST; - // If we are setup to broadcast on the default channel, ensure that the telemetry intervals are coerced to the minimum value - // of 30 minutes or more - if (channels.isDefaultChannel(channels.getPrimaryIndex())) { + // If we are setup to broadcast on any default channel slot (with default frequency slot semantics), + // ensure that the telemetry intervals are coerced to the minimum value of 30 minutes or more. + if (channels.hasDefaultChannel()) { LOG_DEBUG("Coerce telemetry to min of 30 minutes on defaults"); moduleConfig.telemetry.device_update_interval = Default::getConfiguredOrMinimumValue( moduleConfig.telemetry.device_update_interval, min_default_telemetry_interval_secs); From 02d42f87d398af6b7c24323f5ee25bad4658e991 Mon Sep 17 00:00:00 2001 From: Wessel Date: Tue, 24 Feb 2026 15:03:25 +0100 Subject: [PATCH 196/223] Implement 'agc' reset for SX126x & LR11x0 chip families (#9705) * Implement 'agc' reset for SX126x chip family There's no actual agc on SX126x chips but you can reset the analog registers by doing a warm sleep & running calibration. * Address PR comments & implement for LR11x0 too * calibrate for configured frequency band * Gate LR11X0_AGC_RESET --- src/main.cpp | 7 +++++ src/mesh/LR11x0Interface.cpp | 32 ++++++++++++++++++++ src/mesh/LR11x0Interface.h | 4 +++ src/mesh/RadioLibInterface.cpp | 5 ++++ src/mesh/RadioLibInterface.h | 9 ++++++ src/mesh/SX126xInterface.cpp | 55 ++++++++++++++++++++++++++++++++++ src/mesh/SX126xInterface.h | 2 ++ 7 files changed, 114 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index b4c1f6519..5e5bee867 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1125,6 +1125,13 @@ void loop() lastRadioMissedIrqPoll = millis(); RadioLibInterface::instance->pollMissedIrqs(); } + + // Periodic AGC reset — warm sleep + recalibrate to prevent stuck AGC gain + static uint32_t lastAgcReset; + if (!Throttle::isWithinTimespanMs(lastAgcReset, AGC_RESET_INTERVAL_MS)) { + lastAgcReset = millis(); + RadioLibInterface::instance->resetAGC(); + } } #ifdef DEBUG_STACK diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index a6ac4f418..4fec06da4 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -299,6 +299,38 @@ template bool LR11x0Interface::isActivelyReceiving() RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED); } +#ifdef LR11X0_AGC_RESET +template void LR11x0Interface::resetAGC() +{ + // Safety: don't reset mid-packet + if (sendingPacket != NULL || (isReceiving && isActivelyReceiving())) + return; + + LOG_DEBUG("LR11x0 AGC reset: warm sleep + Calibrate(0x3F)"); + + // 1. Warm sleep — powers down the analog frontend, resetting AGC state + lora.sleep(true, 0); + + // 2. Wake to RC standby for stable calibration + lora.standby(RADIOLIB_LR11X0_STANDBY_RC, true); + + // 3. Calibrate all blocks (PLL, ADC, image, RC oscillators) + // calibrate() is protected on LR11x0, so use raw SPI (same as internal implementation) + uint8_t calData = RADIOLIB_LR11X0_CALIBRATE_ALL; + module.SPIwriteStream(RADIOLIB_LR11X0_CMD_CALIBRATE, &calData, 1, true, true); + + // 4. Re-calibrate image rejection for actual operating frequency + // Calibrate(0x3F) defaults to 902-928 MHz which is wrong for other regions. + lora.calibrateImageRejection(getFreq() - 4.0f, getFreq() + 4.0f); + + // 5. Re-apply RX boosted gain mode + lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain); + + // 6. Resume receiving + startReceive(); +} +#endif + template bool LR11x0Interface::sleep() { // \todo Display actual typename of the adapter, not just `LR11x0` diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h index 840184bbf..1a6b92520 100644 --- a/src/mesh/LR11x0Interface.h +++ b/src/mesh/LR11x0Interface.h @@ -27,6 +27,10 @@ template class LR11x0Interface : public RadioLibInterface bool isIRQPending() override { return lora.getIrqFlags() != 0; } +#ifdef LR11X0_AGC_RESET + void resetAGC() override; +#endif + protected: /** * Specific module instance diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 78e0fc5b4..30cd587da 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -529,6 +529,11 @@ void RadioLibInterface::pollMissedIrqs() } } +void RadioLibInterface::resetAGC() +{ + // Base implementation: no-op. Override in chip-specific subclasses. +} + void RadioLibInterface::checkRxDoneIrqFlag() { if (iface->checkIrq(RADIOLIB_IRQ_RX_DONE)) { diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 4bca99b5f..ca3d78503 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -19,6 +19,8 @@ // In addition to the default Rx flags, we need the PREAMBLE_DETECTED flag to detect whether we are actively receiving #define MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS (RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1 << RADIOLIB_IRQ_PREAMBLE_DETECTED)) +#define AGC_RESET_INTERVAL_MS (60 * 1000) // 60 seconds + /** * We need to override the RadioLib ArduinoHal class to add mutex protection for SPI bus access */ @@ -117,6 +119,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified */ void pollMissedIrqs(); + /** + * Reset AGC by power-cycling the analog frontend. + * Subclasses override with chip-specific calibration sequences. + * Safe to call periodically — skips if currently sending or receiving. + */ + virtual void resetAGC(); + /** * Debugging counts */ diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 5b2fb3ac3..a9ee16545 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -434,6 +434,61 @@ template bool SX126xInterface::sleep() return true; } +template void SX126xInterface::resetAGC() +{ + // Safety: don't reset mid-packet + if (sendingPacket != NULL || (isReceiving && isActivelyReceiving())) + return; + + LOG_DEBUG("SX126x AGC reset: warm sleep + Calibrate(0x7F)"); + + // 1. Warm sleep — powers down the entire analog frontend, resetting AGC state. + // A plain standby→startReceive cycle does NOT reset the AGC. + lora.sleep(true); + + // 2. Wake to RC standby for stable calibration + lora.standby(RADIOLIB_SX126X_STANDBY_RC, true); + + // 3. Calibrate all blocks (ADC, PLL, image, RC oscillators) + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + module.SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + + // 4. Wait for calibration to complete (BUSY pin goes low) + module.hal->delay(5); + uint32_t start = millis(); + while (module.hal->digitalRead(module.getGpio())) { + if (millis() - start > 50) + break; + module.hal->yield(); + } + + if (module.hal->digitalRead(module.getGpio())) { + LOG_WARN("SX126x AGC reset: calibration did not complete within 50ms"); + startReceive(); + return; + } + + // 5. Re-calibrate image rejection for actual operating frequency + // Calibrate(0x7F) defaults to 902-928 MHz which is wrong for other regions. + lora.calibrateImage(getFreq()); + + // Re-apply settings that calibration may have reset + + // DIO2 as RF switch +#ifdef SX126X_DIO2_AS_RF_SWITCH + lora.setDio2AsRfSwitch(true); +#elif defined(ARCH_PORTDUINO) + if (portduino_config.dio2_as_rf_switch) + lora.setDio2AsRfSwitch(true); +#endif + + // RX boosted gain mode + lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain); + + // 6. Resume receiving + startReceive(); +} + /** Control PA mode for GC1109 FEM - CPS pin selects full PA (txon=true) or bypass mode (txon=false) */ template void SX126xInterface::setTransmitEnable(bool txon) { diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index b8f16ac6d..67625e115 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -28,6 +28,8 @@ template class SX126xInterface : public RadioLibInterface bool isIRQPending() override { return lora.getIrqFlags() != 0; } + void resetAGC() override; + void setTCXOVoltage(float voltage) { tcxoVoltage = voltage; } protected: From cac45d9cedb1028a36854199bd6e4f836f89ca8f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 24 Feb 2026 13:26:47 -0600 Subject: [PATCH 197/223] Align telemetry broadcast want_response behavior with traceroute (#9717) * Align telemetry broadcast want_response behavior with traceroute * Fixes * Reduce side-effects by making the telemetry modules handle the ignorerequest * Remove unnecessary ignoreRequest flag * Try inheriting from MeshModule * Add exclusion for sensor/router roles and add base telem module --- src/mesh/MeshModule.h | 12 ++ src/modules/Telemetry/AirQualityTelemetry.cpp | 4 + src/modules/Telemetry/AirQualityTelemetry.h | 3 + src/modules/Telemetry/BaseTelemetryModule.h | 14 +++ src/modules/Telemetry/DeviceTelemetry.cpp | 7 +- src/modules/Telemetry/DeviceTelemetry.h | 5 +- .../Telemetry/EnvironmentTelemetry.cpp | 4 + src/modules/Telemetry/EnvironmentTelemetry.h | 3 + src/modules/Telemetry/HealthTelemetry.cpp | 4 + src/modules/Telemetry/HealthTelemetry.h | 5 +- src/modules/Telemetry/PowerTelemetry.cpp | 4 + src/modules/Telemetry/PowerTelemetry.h | 5 +- test/test_mesh_module/test_main.cpp | 116 ++++++++++++++++++ 13 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 src/modules/Telemetry/BaseTelemetryModule.h create mode 100644 test/test_mesh_module/test_main.cpp diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index 63f401d18..9d579d4f1 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -106,6 +106,18 @@ class MeshModule /* We allow modules to ignore a request without sending an error if they have a specific reason for it. */ bool ignoreRequest = false; + /** + * Check if the current request is a multi-hop broadcast. Modules should call this in allocReply() + * and return NULL to prevent reply storms from broadcast requests that have already been relayed. + */ + bool isMultiHopBroadcastRequest() + { + if (currentRequest && isBroadcast(currentRequest->to) && currentRequest->hop_limit < currentRequest->hop_start) { + return true; + } + return false; + } + /** If a bound channel name is set, we will only accept received packets that come in on that channel. * A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface * are allowed on any channel (this lets the local user do anything). diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 5ffe4d992..1e5567d7b 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -321,6 +321,10 @@ bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 197491f2d..9f19e396e 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -4,6 +4,8 @@ #pragma once +#include "BaseTelemetryModule.h" + #ifndef AIR_QUALITY_TELEMETRY_MODULE_ENABLE #define AIR_QUALITY_TELEMETRY_MODULE_ENABLE 0 #endif @@ -17,6 +19,7 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public ScanI2CConsumer, + public BaseTelemetryModule, public ProtobufModule { CallbackObserver nodeStatusObserver = diff --git a/src/modules/Telemetry/BaseTelemetryModule.h b/src/modules/Telemetry/BaseTelemetryModule.h new file mode 100644 index 000000000..b032bef3f --- /dev/null +++ b/src/modules/Telemetry/BaseTelemetryModule.h @@ -0,0 +1,14 @@ +#pragma once + +#include "NodeDB.h" +#include "configuration.h" + +class BaseTelemetryModule +{ + protected: + bool isSensorOrRouterRole() const + { + return config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR || + config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER; + } +}; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index 066b9361d..d09835f95 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -19,8 +19,7 @@ int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); - bool isImpoliteRole = - IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_ROUTER); + bool isImpoliteRole = isSensorOrRouterRole(); if (((lastSentToMesh == 0) || ((uptimeLastMs - lastSentToMesh) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, @@ -60,6 +59,10 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket & meshtastic_MeshPacket *DeviceTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/DeviceTelemetry.h b/src/modules/Telemetry/DeviceTelemetry.h index a1d55a596..0dc431775 100644 --- a/src/modules/Telemetry/DeviceTelemetry.h +++ b/src/modules/Telemetry/DeviceTelemetry.h @@ -1,11 +1,14 @@ #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BaseTelemetryModule.h" #include "NodeDB.h" #include "ProtobufModule.h" #include #include -class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModule +class DeviceTelemetryModule : private concurrency::OSThread, + public BaseTelemetryModule, + public ProtobufModule { CallbackObserver nodeStatusObserver = CallbackObserver(this, &DeviceTelemetryModule::handleStatusUpdate); diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 140c2c17e..896a27275 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -577,6 +577,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index 049ed6b77..fc80a986a 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -4,6 +4,8 @@ #pragma once +#include "BaseTelemetryModule.h" + #ifndef ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE #define ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE 0 #endif @@ -17,6 +19,7 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public ScanI2CConsumer, + public BaseTelemetryModule, public ProtobufModule { CallbackObserver nodeStatusObserver = diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index bb3555062..9c57193cd 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -192,6 +192,10 @@ bool HealthTelemetryModule::getHealthTelemetry(meshtastic_Telemetry *m) meshtastic_MeshPacket *HealthTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/HealthTelemetry.h b/src/modules/Telemetry/HealthTelemetry.h index 01e4c2372..1ab389fbf 100644 --- a/src/modules/Telemetry/HealthTelemetry.h +++ b/src/modules/Telemetry/HealthTelemetry.h @@ -4,12 +4,15 @@ #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BaseTelemetryModule.h" #include "NodeDB.h" #include "ProtobufModule.h" #include #include -class HealthTelemetryModule : private concurrency::OSThread, public ProtobufModule +class HealthTelemetryModule : private concurrency::OSThread, + public BaseTelemetryModule, + public ProtobufModule { CallbackObserver nodeStatusObserver = CallbackObserver(this, &HealthTelemetryModule::handleStatusUpdate); diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 9047c7cd4..7147cb14a 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -217,6 +217,10 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) meshtastic_MeshPacket *PowerTelemetryModule::allocReply() { if (currentRequest) { + if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) { + ignoreRequest = true; + return NULL; + } auto req = *currentRequest; const auto &p = req.decoded; meshtastic_Telemetry scratch; diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h index b9ec6edc1..97cefb4a5 100644 --- a/src/modules/Telemetry/PowerTelemetry.h +++ b/src/modules/Telemetry/PowerTelemetry.h @@ -5,12 +5,15 @@ #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "BaseTelemetryModule.h" #include "NodeDB.h" #include "ProtobufModule.h" #include #include -class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModule +class PowerTelemetryModule : private concurrency::OSThread, + public BaseTelemetryModule, + public ProtobufModule { CallbackObserver nodeStatusObserver = CallbackObserver(this, &PowerTelemetryModule::handleStatusUpdate); diff --git a/test/test_mesh_module/test_main.cpp b/test/test_mesh_module/test_main.cpp new file mode 100644 index 000000000..ec7b1808e --- /dev/null +++ b/test/test_mesh_module/test_main.cpp @@ -0,0 +1,116 @@ +#include "MeshModule.h" +#include "MeshTypes.h" +#include "TestUtil.h" +#include + +// Minimal concrete subclass for testing the base class helper +class TestModule : public MeshModule +{ + public: + TestModule() : MeshModule("TestModule") {} + virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return true; } + using MeshModule::currentRequest; + using MeshModule::isMultiHopBroadcastRequest; +}; + +static TestModule *testModule; +static meshtastic_MeshPacket testPacket; + +void setUp(void) +{ + testModule = new TestModule(); + memset(&testPacket, 0, sizeof(testPacket)); + TestModule::currentRequest = &testPacket; +} + +void tearDown(void) +{ + TestModule::currentRequest = NULL; + delete testModule; +} + +// Zero-hop broadcast (hop_limit == hop_start): should be allowed +static void test_zeroHopBroadcast_isAllowed() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 3; + testPacket.hop_limit = 3; // Not yet relayed + + TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest()); +} + +// Multi-hop broadcast (hop_limit < hop_start): should be blocked +static void test_multiHopBroadcast_isBlocked() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 7; + testPacket.hop_limit = 4; // Already relayed 3 hops + + TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest()); +} + +// Direct message (not broadcast): should always be allowed regardless of hops +static void test_directMessage_isAllowed() +{ + testPacket.to = 0x12345678; // Specific node + testPacket.hop_start = 7; + testPacket.hop_limit = 4; + + TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest()); +} + +// Broadcast with hop_limit == 0 (fully relayed): should be blocked +static void test_fullyRelayedBroadcast_isBlocked() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 3; + testPacket.hop_limit = 0; + + TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest()); +} + +// No current request: should not crash, should return false +static void test_noCurrentRequest_isAllowed() +{ + TestModule::currentRequest = NULL; + + TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest()); +} + +// Broadcast with hop_start == 0 (legacy or local): should be allowed +static void test_legacyPacket_zeroHopStart_isAllowed() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 0; + testPacket.hop_limit = 0; + + // hop_limit == hop_start, so not multi-hop + TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest()); +} + +// Single hop relayed broadcast (hop_limit = hop_start - 1): should be blocked +static void test_singleHopRelayedBroadcast_isBlocked() +{ + testPacket.to = NODENUM_BROADCAST; + testPacket.hop_start = 3; + testPacket.hop_limit = 2; + + TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest()); +} + +void setup() +{ + initializeTestEnvironment(); + + UNITY_BEGIN(); + RUN_TEST(test_zeroHopBroadcast_isAllowed); + RUN_TEST(test_multiHopBroadcast_isBlocked); + RUN_TEST(test_directMessage_isAllowed); + RUN_TEST(test_fullyRelayedBroadcast_isBlocked); + RUN_TEST(test_noCurrentRequest_isAllowed); + RUN_TEST(test_legacyPacket_zeroHopStart_isAllowed); + RUN_TEST(test_singleHopRelayedBroadcast_isBlocked); + exit(UNITY_END()); +} + +void loop() {} From c65eecc29523808e4ca9f170a857f201dde32e5b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:27:23 -0600 Subject: [PATCH 198/223] Update protobufs (#9739) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/admin.pb.h | 8 +++-- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 14 +++++--- src/mesh/generated/meshtastic/mesh.pb.h | 4 +++ .../generated/meshtastic/module_config.pb.cpp | 3 ++ .../generated/meshtastic/module_config.pb.h | 34 ++++++++++++++++++- 7 files changed, 57 insertions(+), 10 deletions(-) diff --git a/protobufs b/protobufs index 44298d374..f7f7c8d2e 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 44298d374fd83cfbc36fdb76c6f966e980cadd93 +Subproject commit f7f7c8d2e4bf27013efe833d322a2306f2514c39 diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 3dd92997d..d529336c8 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -81,7 +81,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13, /* Traffic management module config */ - meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14 + meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14, + /* TAK module config */ + meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG = 15 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -395,8 +397,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 5bbd87ffd..1d9e9baf8 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2419 +#define meshtastic_BackupPreferences_size 2426 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 7a5874b64..e0bd95d40 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -93,6 +93,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* The part of the config that is specific to the Traffic Management module */ bool has_traffic_management; meshtastic_ModuleConfig_TrafficManagementConfig traffic_management; + /* TAK Config */ + bool has_tak; + meshtastic_ModuleConfig_TAKConfig tak; } meshtastic_LocalModuleConfig; @@ -102,9 +105,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default, false, meshtastic_ModuleConfig_TAKConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero, false, meshtastic_ModuleConfig_TAKConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -132,6 +135,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_paxcounter_tag 14 #define meshtastic_LocalModuleConfig_statusmessage_tag 15 #define meshtastic_LocalModuleConfig_traffic_management_tag 16 +#define meshtastic_LocalModuleConfig_tak_tag 17 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -171,7 +175,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) \ -X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16) +X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16) \ +X(a, STATIC, OPTIONAL, MESSAGE, tak, 17) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -189,6 +194,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16) #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig #define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig #define meshtastic_LocalModuleConfig_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig +#define meshtastic_LocalModuleConfig_tak_MSGTYPE meshtastic_ModuleConfig_TAKConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -200,7 +206,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 751 -#define meshtastic_LocalModuleConfig_size 813 +#define meshtastic_LocalModuleConfig_size 820 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index aeae4bd84..773cc8ed6 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -300,6 +300,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_TBEAM_1_WATT = 122, /* LilyGo T5 S3 ePaper Pro (V1 and V2) */ meshtastic_HardwareModel_T5_S3_EPAPER_PRO = 123, + /* LilyGo T-Beam BPF (144-148Mhz) */ + meshtastic_HardwareModel_TBEAM_BPF = 124, + /* LilyGo T-Mini E-paper S3 Kit */ + meshtastic_HardwareModel_MINI_EPAPER_S3 = 125, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index b9705b0bc..f2fe5d967 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -57,6 +57,9 @@ PB_BIND(meshtastic_ModuleConfig_AmbientLightingConfig, meshtastic_ModuleConfig_A PB_BIND(meshtastic_ModuleConfig_StatusMessageConfig, meshtastic_ModuleConfig_StatusMessageConfig, AUTO) +PB_BIND(meshtastic_ModuleConfig_TAKConfig, meshtastic_ModuleConfig_TAKConfig, AUTO) + + PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO) diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index 67dbe06e7..b8cf60bf0 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -4,6 +4,7 @@ #ifndef PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED #define PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED #include +#include "meshtastic/atak.pb.h" #if PB_PROTO_HEADER_VERSION != 40 #error Regenerate this file with the current version of nanopb generator. @@ -448,6 +449,16 @@ typedef struct _meshtastic_ModuleConfig_StatusMessageConfig { char node_status[80]; } meshtastic_ModuleConfig_StatusMessageConfig; +/* TAK team/role configuration */ +typedef struct _meshtastic_ModuleConfig_TAKConfig { + /* Team color. + Default Unspecifed_Color -> firmware uses Cyan */ + meshtastic_Team team; + /* Member role. + Default Unspecifed -> firmware uses TeamMember */ + meshtastic_MemberRole role; +} meshtastic_ModuleConfig_TAKConfig; + /* A GPIO pin definition for remote hardware module */ typedef struct _meshtastic_RemoteHardwarePin { /* GPIO Pin number (must match Arduino) */ @@ -503,6 +514,8 @@ typedef struct _meshtastic_ModuleConfig { meshtastic_ModuleConfig_StatusMessageConfig statusmessage; /* Traffic management module config for mesh network optimization */ meshtastic_ModuleConfig_TrafficManagementConfig traffic_management; + /* TAK team/role configuration for TAK_TRACKER */ + meshtastic_ModuleConfig_TAKConfig tak; } payload_variant; } meshtastic_ModuleConfig; @@ -560,6 +573,9 @@ extern "C" { +#define meshtastic_ModuleConfig_TAKConfig_team_ENUMTYPE meshtastic_Team +#define meshtastic_ModuleConfig_TAKConfig_role_ENUMTYPE meshtastic_MemberRole + #define meshtastic_RemoteHardwarePin_type_ENUMTYPE meshtastic_RemoteHardwarePinType @@ -581,6 +597,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""} +#define meshtastic_ModuleConfig_TAKConfig_init_default {_meshtastic_Team_MIN, _meshtastic_MemberRole_MIN} #define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN} #define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}} #define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero} @@ -599,6 +616,7 @@ extern "C" { #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""} +#define meshtastic_ModuleConfig_TAKConfig_init_zero {_meshtastic_Team_MIN, _meshtastic_MemberRole_MIN} #define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN} /* Field tags (for use in manual encoding/decoding) */ @@ -717,6 +735,8 @@ extern "C" { #define meshtastic_ModuleConfig_AmbientLightingConfig_green_tag 4 #define meshtastic_ModuleConfig_AmbientLightingConfig_blue_tag 5 #define meshtastic_ModuleConfig_StatusMessageConfig_node_status_tag 1 +#define meshtastic_ModuleConfig_TAKConfig_team_tag 1 +#define meshtastic_ModuleConfig_TAKConfig_role_tag 2 #define meshtastic_RemoteHardwarePin_gpio_pin_tag 1 #define meshtastic_RemoteHardwarePin_name_tag 2 #define meshtastic_RemoteHardwarePin_type_tag 3 @@ -738,6 +758,7 @@ extern "C" { #define meshtastic_ModuleConfig_paxcounter_tag 13 #define meshtastic_ModuleConfig_statusmessage_tag 14 #define meshtastic_ModuleConfig_traffic_management_tag 15 +#define meshtastic_ModuleConfig_tak_tag 16 /* Struct field encoding specification for nanopb */ #define meshtastic_ModuleConfig_FIELDLIST(X, a) \ @@ -755,7 +776,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_var X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) \ -X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15) +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15) \ +X(a, STATIC, ONEOF, MESSAGE, (payload_variant,tak,payload_variant.tak), 16) #define meshtastic_ModuleConfig_CALLBACK NULL #define meshtastic_ModuleConfig_DEFAULT NULL #define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -773,6 +795,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_v #define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig #define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig #define meshtastic_ModuleConfig_payload_variant_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig +#define meshtastic_ModuleConfig_payload_variant_tak_MSGTYPE meshtastic_ModuleConfig_TAKConfig #define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ @@ -958,6 +981,12 @@ X(a, STATIC, SINGULAR, STRING, node_status, 1) #define meshtastic_ModuleConfig_StatusMessageConfig_CALLBACK NULL #define meshtastic_ModuleConfig_StatusMessageConfig_DEFAULT NULL +#define meshtastic_ModuleConfig_TAKConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, team, 1) \ +X(a, STATIC, SINGULAR, UENUM, role, 2) +#define meshtastic_ModuleConfig_TAKConfig_CALLBACK NULL +#define meshtastic_ModuleConfig_TAKConfig_DEFAULT NULL + #define meshtastic_RemoteHardwarePin_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, gpio_pin, 1) \ X(a, STATIC, SINGULAR, STRING, name, 2) \ @@ -982,6 +1011,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_TelemetryConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_CannedMessageConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_AmbientLightingConfig_msg; extern const pb_msgdesc_t meshtastic_ModuleConfig_StatusMessageConfig_msg; +extern const pb_msgdesc_t meshtastic_ModuleConfig_TAKConfig_msg; extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ @@ -1002,6 +1032,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_CannedMessageConfig_fields &meshtastic_ModuleConfig_CannedMessageConfig_msg #define meshtastic_ModuleConfig_AmbientLightingConfig_fields &meshtastic_ModuleConfig_AmbientLightingConfig_msg #define meshtastic_ModuleConfig_StatusMessageConfig_fields &meshtastic_ModuleConfig_StatusMessageConfig_msg +#define meshtastic_ModuleConfig_TAKConfig_fields &meshtastic_ModuleConfig_TAKConfig_msg #define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg /* Maximum encoded size of messages (where known) */ @@ -1020,6 +1051,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StatusMessageConfig_size 81 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 +#define meshtastic_ModuleConfig_TAKConfig_size 4 #define meshtastic_ModuleConfig_TelemetryConfig_size 50 #define meshtastic_ModuleConfig_TrafficManagementConfig_size 52 #define meshtastic_ModuleConfig_size 227 From 58496e56d2f193dcfc816fa20c71b2f2016cde87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 24 Feb 2026 22:45:38 +0100 Subject: [PATCH 199/223] fail cppcheck on low already. Code quality for the win. --- .github/workflows/main_matrix.yml | 1 - bin/check-all.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 876d7e4ce..6b48e8128 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -96,7 +96,6 @@ jobs: pio_platform: ${{ matrix.check.platform }} pio_env: ${{ matrix.check.board }} pio_target: check - pio_opts: --fail-on-defect=low build: needs: [setup, version] diff --git a/bin/check-all.sh b/bin/check-all.sh index 29d6b5532..9c7fc694d 100755 --- a/bin/check-all.sh +++ b/bin/check-all.sh @@ -23,4 +23,4 @@ for BOARD in $BOARDS; do CHECK="${CHECK} -e ${BOARD}" done -pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=medium --fail-on-defect=high +pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=low --fail-on-defect=medium --fail-on-defect=high From ad7d19c317896e4b37fad3289bc89b886b6b5c24 Mon Sep 17 00:00:00 2001 From: Wessel Date: Wed, 25 Feb 2026 03:57:58 +0100 Subject: [PATCH 200/223] Remove unused global rIf that shadows locals and fails cppcheck (#9743) I noticed because my PR failed --- src/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 5e5bee867..f4deb0de5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -246,7 +246,6 @@ uint32_t timeLastPowered = 0; static OSThread *powerFSMthread; OSThread *ambientLightingThread; -RadioInterface *rIf = NULL; RadioLibHal *RadioLibHAL = NULL; /** From 54781cf51a5f209a710afe54e54ec87b25cf26c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 25 Feb 2026 10:27:21 +0100 Subject: [PATCH 201/223] hopefully fix remaining cppcheck issues (#9745) --- src/AudioThread.h | 4 +--- src/SerialConsole.cpp | 2 ++ src/graphics/TFTDisplay.cpp | 4 ++-- .../Applets/User/FavoritesMap/FavoritesMapApplet.cpp | 2 +- src/mesh/NodeDB.cpp | 5 ++--- src/mesh/udp/UdpMulticastHandler.h | 2 +- src/modules/Telemetry/EnvironmentTelemetry.cpp | 11 +++++++---- src/modules/Telemetry/Sensor/RAK9154Sensor.h | 1 + 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/AudioThread.h b/src/AudioThread.h index e1ba422bc..1129ee087 100644 --- a/src/AudioThread.h +++ b/src/AudioThread.h @@ -51,9 +51,7 @@ class AudioThread : public concurrency::OSThread i2sRtttl = nullptr; } - if (rtttlFile != nullptr) { - rtttlFile = nullptr; - } + rtttlFile = nullptr; setCPUFast(false); #ifdef T_LORA_PAGER diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index dd2acb599..e24aa3c57 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -35,6 +35,8 @@ void consoleInit() #if defined(SERIAL_HAS_ON_RECEIVE) // onReceive does only exist for HardwareSerial not for USB CDC serial Port.onReceive([sc]() { sc->rxInt(); }); +#else + (void)sc; #endif DEBUG_PORT.rpInit(); // Simply sets up semaphore } diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 12fac4f34..1c2eb72d4 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1209,8 +1209,8 @@ void TFTDisplay::display(bool fromBlank) bool somethingChanged = false; // Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step - colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8); - colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8); + colorTftMesh = __builtin_bswap16(TFT_MESH); + colorTftBlack = __builtin_bswap16(TFT_BLACK); y = 0; while (y < displayHeight) { diff --git a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp index 6963df54a..520070d72 100644 --- a/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.cpp @@ -73,7 +73,7 @@ ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshP } } else { // For non-local packets, this applet only reacts to favorited nodes. - meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); + const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from); if (!sender || !sender->is_favorite) return ProcessMessage::CONTINUE; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 50f43b2c4..360253041 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -143,9 +143,8 @@ uint32_t get_st7789_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_ digitalWrite(rst, HIGH); delay(10); - uint32_t ID = 0; - ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); - ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); // ST7789 needs twice + readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); + uint32_t ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); // ST7789 needs twice return ID; } diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h index d79b63ceb..71bdf488f 100644 --- a/src/mesh/udp/UdpMulticastHandler.h +++ b/src/mesh/udp/UdpMulticastHandler.h @@ -56,7 +56,7 @@ class UdpMulticastHandler final isRunning = false; } - void onReceive(AsyncUDPPacket packet) + void onReceive(AsyncUDPPacket &packet) { if (!isRunning) { return; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 896a27275..09f7be925 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -297,7 +297,8 @@ int32_t EnvironmentTelemetryModule::runOnce() // this only works on the wismesh hub with the solar option. This is not an I2C sensor, so we don't need the // sensormap here. #ifdef HAS_RAKPROT - result = rak9154Sensor.runOnce(); + if (rak9154Sensor.hasSensor()) + result = rak9154Sensor.runOnce(); #endif #endif } @@ -567,9 +568,11 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m } #endif #ifdef HAS_RAKPROT - get_metrics = rak9154Sensor.getMetrics(m); - valid = valid || get_metrics; - hasSensor = true; + if (rak9154Sensor.hasSensor()) { + get_metrics = rak9154Sensor.getMetrics(m); + valid = valid || get_metrics; + hasSensor = true; + } #endif return valid && hasSensor; } diff --git a/src/modules/Telemetry/Sensor/RAK9154Sensor.h b/src/modules/Telemetry/Sensor/RAK9154Sensor.h index c96139f9c..34d0fba73 100644 --- a/src/modules/Telemetry/Sensor/RAK9154Sensor.h +++ b/src/modules/Telemetry/Sensor/RAK9154Sensor.h @@ -18,6 +18,7 @@ class RAK9154Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor public: RAK9154Sensor(); + bool hasSensor() { return true; } // Not an I2C sensor; always available when HAS_RAKPROT is defined virtual int32_t runOnce() override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual uint16_t getBusVoltageMv() override; From a93d4d7e9d3f50d4d13df9f616456022009d78f5 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Feb 2026 08:26:49 -0600 Subject: [PATCH 202/223] Revert "Fix/rak3401 button (#9668)" (#9747) This reverts commit a5523b04ef30bb5c62a56abd2f49c5a592b628ad. --- variants/nrf52840/rak3401_1watt/variant.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h index 9f81a15ba..80b09cf69 100644 --- a/variants/nrf52840/rak3401_1watt/variant.h +++ b/variants/nrf52840/rak3401_1watt/variant.h @@ -52,12 +52,6 @@ extern "C" { #define LED_STATE_ON 1 // State when LED is litted -/* - * Buttons - */ -#define PIN_BUTTON1 (9) -#define BUTTON_NEED_PULLUP - /* * Analog pins */ From 25f086a64f51d482673fc1e35f69396e36dc18eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:30:32 -0600 Subject: [PATCH 203/223] Upgrade trunk (#9744) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index a1d07145a..cdd08cfaf 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,11 +9,11 @@ plugins: lint: enabled: - checkov@3.2.506 - - renovate@43.31.9 + - renovate@43.35.1 - prettier@3.8.1 - trufflehog@3.93.4 - yamllint@1.38.0 - - bandit@1.9.3 + - bandit@1.9.4 - trivy@0.69.1 - taplo@0.10.0 - ruff@0.15.2 From 5a068431ed69c9edb8f29415d7b7fce821c64a17 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 25 Feb 2026 14:18:07 -0600 Subject: [PATCH 204/223] Add GPIO_DETECT_PA portduino config, and support 13302 detection with it (#9741) * Add GPIO_DETECT_PA portduino config, and support 13302 detection with it. * Tweak PA detect gpio to use pinMapping * minor yaml output fixes --------- Co-authored-by: Ben Meadors --- bin/config.d/lora-hat-rak-6421-pi-hat.yaml | 2 ++ bin/config.d/lora-usb-umesh-1262-30dbm.yaml | 3 +-- bin/config.d/lora-usb-umesh-1268-30dbm.yaml | 3 +-- src/platform/portduino/PortduinoGlue.cpp | 21 +++++++++++++++++++++ src/platform/portduino/PortduinoGlue.h | 15 +++++++++++---- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/bin/config.d/lora-hat-rak-6421-pi-hat.yaml b/bin/config.d/lora-hat-rak-6421-pi-hat.yaml index 066e36a10..80df34ca7 100644 --- a/bin/config.d/lora-hat-rak-6421-pi-hat.yaml +++ b/bin/config.d/lora-hat-rak-6421-pi-hat.yaml @@ -9,3 +9,5 @@ Lora: DIO3_TCXO_VOLTAGE: true DIO2_AS_RF_SWITCH: true spidev: spidev0.0 + GPIO_DETECT_PA: 13 + TX_GAIN_LORA: [8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7] \ No newline at end of file diff --git a/bin/config.d/lora-usb-umesh-1262-30dbm.yaml b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml index 7726eccd1..8b32c5af2 100644 --- a/bin/config.d/lora-usb-umesh-1262-30dbm.yaml +++ b/bin/config.d/lora-usb-umesh-1262-30dbm.yaml @@ -13,8 +13,7 @@ Lora: # USB_Serialnum: 12345678 SX126X_MAX_POWER: 22 # Reduce output power to improve EMI - NUM_PA_POINTS: 22 - TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 + TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7] # Note: This module integrates an additional PA to achieve higher output power. # The 'power' parameter here does not represent the actual RF output. # TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). diff --git a/bin/config.d/lora-usb-umesh-1268-30dbm.yaml b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml index c054a92f9..df772184c 100644 --- a/bin/config.d/lora-usb-umesh-1268-30dbm.yaml +++ b/bin/config.d/lora-usb-umesh-1268-30dbm.yaml @@ -13,8 +13,7 @@ Lora: # USB_Serialnum: 12345678 SX126X_MAX_POWER: 22 # Reduce output power to improve EMI - NUM_PA_POINTS: 22 - TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7 + TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7] # Note: This module integrates an additional PA to achieve higher output power. # The 'power' parameter here does not represent the actual RF output. # TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm). diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 3f0b2147c..2cf23332c 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -537,6 +537,27 @@ void portduinoSetup() } } + // In one test, this dance seemed necessary to trigger the pin to detect properly. + if (portduino_config.lora_pa_detect_pin.enabled) { + pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT_PULLDOWN); + sleep(1); + if (digitalRead(portduino_config.lora_pa_detect_pin.pin) == LOW) { + std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLDOWN is LOW" << std::endl; + } + pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT_PULLUP); + sleep(1); + if (digitalRead(portduino_config.lora_pa_detect_pin.pin) == HIGH) { + std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLUP is HIGH, dropping PA curve" << std::endl; + portduino_config.num_pa_points = 1; + portduino_config.tx_gain_lora[0] = 0; + } else { + std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLUP is LOW, using PA curve" << std::endl; + } + + // disable bias once finished + pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT); + } + // Only initialize the radio pins when dealing with real, kernel controlled SPI hardware if (portduino_config.lora_spi_dev != "" && portduino_config.lora_spi_dev != "ch341") { SPI.begin(portduino_config.lora_spi_dev.c_str()); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index aa8847fd7..0bd3582de 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -107,6 +107,7 @@ extern struct portduino_config_struct { pinMapping lora_txen_pin = {"Lora", "TXen"}; pinMapping lora_rxen_pin = {"Lora", "RXen"}; pinMapping lora_sx126x_ant_sw_pin = {"Lora", "SX126X_ANT_SW"}; + pinMapping lora_pa_detect_pin = {"Lora", "GPIO_DETECT_PA"}; std::vector extra_pins = {}; // GPS @@ -194,13 +195,14 @@ extern struct portduino_config_struct { int maxtophone = 100; int MaxNodes = 200; - pinMapping *all_pins[20] = {&lora_cs_pin, + pinMapping *all_pins[21] = {&lora_cs_pin, &lora_irq_pin, &lora_busy_pin, &lora_reset_pin, &lora_txen_pin, &lora_rxen_pin, &lora_sx126x_ant_sw_pin, + &lora_pa_detect_pin, &displayDC, &displayCS, &displayBacklight, @@ -255,18 +257,23 @@ extern struct portduino_config_struct { out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << tx_gain_lora[0]; } - out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; + if (dio2_as_rf_switch) + out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; if (dio3_tcxo_voltage != 0) out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000; if (lora_usb_pid != 0x5512) out << YAML::Key << "USB_PID" << YAML::Value << YAML::Hex << lora_usb_pid; if (lora_usb_vid != 0x1A86) out << YAML::Key << "USB_VID" << YAML::Value << YAML::Hex << lora_usb_vid; - if (lora_spi_dev != "") + if (lora_spi_dev != "" && !(lora_spi_dev == "/dev/spidev0.0" && lora_module == use_autoconf)) { + if (lora_spi_dev.find("/dev/") != std::string::npos) + lora_spi_dev = lora_spi_dev.substr(5); out << YAML::Key << "spidev" << YAML::Value << lora_spi_dev; + } if (lora_usb_serial_num != "") out << YAML::Key << "USB_Serialnum" << YAML::Value << lora_usb_serial_num; - out << YAML::Key << "spiSpeed" << YAML::Value << spiSpeed; + if (spiSpeed != 2000000) + out << YAML::Key << "spiSpeed" << YAML::Value << spiSpeed; if (rfswitch_dio_pins[0] != RADIOLIB_NC) { out << YAML::Key << "rfswitch_table" << YAML::Value << YAML::BeginMap; From 3b1b3083572458fce7f3927efc7ef8b150f0ea79 Mon Sep 17 00:00:00 2001 From: Koko Date: Thu, 26 Feb 2026 01:00:02 +0100 Subject: [PATCH 205/223] platform: nrf52: Fix typo in BLEDfuSecure filename (#9709) Change file name from BLEDfuScure.cpp to BLEDfuSecure.cpp. Fix filenames in documentation. Signed-off-by: Koko Co-authored-by: Ben Meadors --- src/platform/nrf52/{BLEDfuScure.cpp => BLEDfuSecure.cpp} | 2 +- src/platform/nrf52/BLEDfuSecure.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/platform/nrf52/{BLEDfuScure.cpp => BLEDfuSecure.cpp} (99%) diff --git a/src/platform/nrf52/BLEDfuScure.cpp b/src/platform/nrf52/BLEDfuSecure.cpp similarity index 99% rename from src/platform/nrf52/BLEDfuScure.cpp rename to src/platform/nrf52/BLEDfuSecure.cpp index 82cb8905a..040df8bdf 100644 --- a/src/platform/nrf52/BLEDfuScure.cpp +++ b/src/platform/nrf52/BLEDfuSecure.cpp @@ -1,6 +1,6 @@ /**************************************************************************/ /*! - @file BLEDfu.cpp + @file BLEDfuSecure.cpp @author hathach (tinyusb.org) @section LICENSE diff --git a/src/platform/nrf52/BLEDfuSecure.h b/src/platform/nrf52/BLEDfuSecure.h index bd5d910e8..dc52d3940 100644 --- a/src/platform/nrf52/BLEDfuSecure.h +++ b/src/platform/nrf52/BLEDfuSecure.h @@ -1,6 +1,6 @@ /**************************************************************************/ /*! - @file BLEDfu.h + @file BLEDfuSecure.h @author hathach (tinyusb.org) @section LICENSE From 3a74e049ab19767f8962a1d0094a6b2036b8d628 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 25 Feb 2026 20:41:07 -0600 Subject: [PATCH 206/223] Add Transmit history persistence for respecting traffic intervals between reboots (#9748) * Add transmit history for throttling that persists between reboots * Fix RAK long press detection to prevent phantom shutdowns from floating pins * Update test/test_transmit_history/test_main.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Test fixes and placeholder for content handler tests --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Dockerfile.test | 26 ++ bin/test-native-docker.sh | 44 ++++ src/input/ButtonThread.cpp | 3 +- src/main.cpp | 4 + src/mesh/TransmitHistory.cpp | 202 +++++++++++++++ src/mesh/TransmitHistory.h | 88 +++++++ src/modules/NodeInfoModule.cpp | 9 +- src/modules/NodeInfoModule.h | 1 - src/modules/PositionModule.cpp | 12 + src/modules/Telemetry/AirQualityTelemetry.cpp | 20 +- src/modules/Telemetry/AirQualityTelemetry.h | 1 - src/modules/Telemetry/DeviceTelemetry.cpp | 14 +- src/modules/Telemetry/DeviceTelemetry.h | 1 - .../Telemetry/EnvironmentTelemetry.cpp | 16 +- src/modules/Telemetry/EnvironmentTelemetry.h | 1 - src/modules/Telemetry/HealthTelemetry.cpp | 15 +- src/modules/Telemetry/HealthTelemetry.h | 1 - src/modules/Telemetry/PowerTelemetry.cpp | 9 +- src/modules/Telemetry/PowerTelemetry.h | 1 - src/sleep.cpp | 5 + test/test_http_content_handler/test_main.cpp | 19 ++ test/test_mqtt/MQTT.cpp | 2 +- test/test_transmit_history/test_main.cpp | 230 ++++++++++++++++++ 23 files changed, 689 insertions(+), 35 deletions(-) create mode 100644 Dockerfile.test create mode 100755 bin/test-native-docker.sh create mode 100644 src/mesh/TransmitHistory.cpp create mode 100644 src/mesh/TransmitHistory.h create mode 100644 test/test_http_content_handler/test_main.cpp create mode 100644 test/test_transmit_history/test_main.cpp diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 000000000..12479b36d --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,26 @@ +# Lightweight container for running native PlatformIO tests on non-Linux hosts +FROM python:3.14-slim-trixie + +ENV DEBIAN_FRONTEND=noninteractive +ENV PIP_ROOT_USER_ACTION=ignore + +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install --no-install-recommends -y \ + g++ git ca-certificates pkg-config \ + libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \ + libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \ + libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \ + && apt-get clean && rm -rf /var/lib/apt/lists/* \ + && pip install --no-cache-dir platformio==6.1.19 \ + && useradd --create-home --shell /usr/sbin/nologin meshtastic + +WORKDIR /firmware +RUN chown -R meshtastic:meshtastic /firmware + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD platformio --version || exit 1 + +USER meshtastic + +# Run tests by default; override with docker run args for specific filters +CMD ["platformio", "test", "-e", "coverage", "-v"] diff --git a/bin/test-native-docker.sh b/bin/test-native-docker.sh new file mode 100755 index 000000000..b42c940c5 --- /dev/null +++ b/bin/test-native-docker.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Run native PlatformIO tests inside Docker (for macOS / non-Linux hosts). +# +# Usage: +# ./bin/test-native-docker.sh # run all native tests +# ./bin/test-native-docker.sh -f test_transmit_history # run specific test filter +# ./bin/test-native-docker.sh --rebuild # force rebuild the image +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +IMAGE_NAME="meshtastic-native-test" + +REBUILD=false +EXTRA_ARGS=() + +for arg in "$@"; do + if [[ "$arg" == "--rebuild" ]]; then + REBUILD=true + else + EXTRA_ARGS+=("$arg") + fi +done + +if $REBUILD || ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then + echo "Building test image (first run may take a few minutes)..." + docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/Dockerfile.test" "$ROOT_DIR" +fi + +# Disable BUILD_EPOCH to avoid full rebuilds between test runs (matches CI) +sed_cmd='s/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/' + +# Default: run all tests. Pass extra args (e.g. -f test_transmit_history) through. +if [[ ${#EXTRA_ARGS[@]} -eq 0 ]]; then + CMD=("platformio" "test" "-e" "coverage" "-v") +else + CMD=("platformio" "test" "-e" "coverage" "-v" "${EXTRA_ARGS[@]}") +fi + +exec docker run --rm \ + -v "$ROOT_DIR:/src:ro" \ + "$IMAGE_NAME" \ + bash -c "rm -rf /tmp/fw-test && cp -a /src /tmp/fw-test && cd /tmp/fw-test && sed -i '${sed_cmd}' platformio.ini && ${CMD[*]}" diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp index 0d835a3a9..3e4aa4bcd 100644 --- a/src/input/ButtonThread.cpp +++ b/src/input/ButtonThread.cpp @@ -276,7 +276,8 @@ int32_t ButtonThread::runOnce() case BUTTON_EVENT_LONG_RELEASED: { LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime); - if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE && + // Require press started after boot holdoff to avoid phantom shutdown from floating pins + if (millis() > 30000 && buttonPressStartTime > 30000 && _longLongPress != INPUT_BROKER_NONE && (millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) { evt.inputEvent = _longLongPress; this->notifyObservers(&evt); diff --git a/src/main.cpp b/src/main.cpp index f4deb0de5..4c686937b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include "PowerMon.h" #include "RadioLibInterface.h" #include "ReliableRouter.h" +#include "TransmitHistory.h" #include "airtime.h" #include "buzz.h" #include "power/PowerHAL.h" @@ -703,6 +704,9 @@ void setup() // We do this as early as possible because this loads preferences from flash // but we need to do this after main cpu init (esp32setup), because we need the random seed set nodeDB = new NodeDB; + + // Initialize transmit history to persist broadcast throttle timers across reboots + TransmitHistory::getInstance()->loadFromDisk(); #if HAS_TFT if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { tftSetup(); diff --git a/src/mesh/TransmitHistory.cpp b/src/mesh/TransmitHistory.cpp new file mode 100644 index 000000000..3dbabc635 --- /dev/null +++ b/src/mesh/TransmitHistory.cpp @@ -0,0 +1,202 @@ +#include "TransmitHistory.h" +#include "FSCommon.h" +#include "RTC.h" +#include "SPILock.h" +#include + +#ifdef FSCom + +TransmitHistory *transmitHistory = nullptr; + +TransmitHistory *TransmitHistory::getInstance() +{ + if (!transmitHistory) { + transmitHistory = new TransmitHistory(); + } + return transmitHistory; +} + +void TransmitHistory::loadFromDisk() +{ + spiLock->lock(); + auto file = FSCom.open(FILENAME, FILE_O_READ); + if (file) { + FileHeader header{}; + if (file.read((uint8_t *)&header, sizeof(header)) == sizeof(header) && header.magic == MAGIC && + header.version == VERSION && header.count <= MAX_ENTRIES) { + for (uint8_t i = 0; i < header.count; i++) { + Entry entry{}; + if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry)) { + if (entry.epochSeconds > 0) { + history[entry.key] = entry.epochSeconds; + // Seed in-memory millis so throttle works even without RTC/GPS. + // Treating stored entries as "just sent" is safe — worst case the + // node waits one full interval before its first broadcast. + lastMillis[entry.key] = millis(); + } + } + } + LOG_INFO("TransmitHistory: loaded %u entries from disk", header.count); + } else { + LOG_WARN("TransmitHistory: invalid file header, starting fresh"); + } + file.close(); + } else { + LOG_INFO("TransmitHistory: no history file found, starting fresh"); + } + spiLock->unlock(); + dirty = false; +} + +void TransmitHistory::setLastSentToMesh(uint16_t key) +{ + lastMillis[key] = millis(); + uint32_t now = getTime(); + if (now >= 2) { + history[key] = now; + dirty = true; + // Don't flush to disk on every transmit — flash has limited write endurance. + // The in-memory lastMillis map handles throttle during normal operation. + // Disk is flushed: before deep sleep (sleep.cpp) and periodically here, + // throttled to at most once per 5 minutes. Always save the first time + // after boot so a crash-reboot loop can't avoid persisting. + if (lastDiskSave == 0 || !Throttle::isWithinTimespanMs(lastDiskSave, SAVE_INTERVAL_MS)) { + if (saveToDisk()) { + lastDiskSave = millis(); + } + } + } +} + +uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const +{ + auto it = history.find(key); + if (it != history.end()) { + return it->second; + } + return 0; +} + +uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const +{ + // Prefer runtime millis value (accurate within this boot) + auto mit = lastMillis.find(key); + if (mit != lastMillis.end()) { + return mit->second; + } + + // Fall back to epoch conversion (loaded from disk after reboot) + uint32_t storedEpoch = getLastSentToMeshEpoch(key); + if (storedEpoch == 0) { + return 0; // No stored time — module has never sent + } + + uint32_t now = getTime(); + if (now < 2) { + // No valid RTC time yet — can't convert to millis. Return 0 so throttle doesn't block. + return 0; + } + + if (storedEpoch > now) { + // Stored time is in the future (clock went backwards?) — treat as stale + return 0; + } + + uint32_t secondsAgo = now - storedEpoch; + uint32_t msAgo = secondsAgo * 1000; + + // Guard against overflow: if the transmit was very long ago, just return 0 (won't throttle) + if (secondsAgo > 86400 || msAgo / 1000 != secondsAgo) { + return 0; + } + + // Convert to a millis()-relative timestamp: millis() - msAgo + // This gives a value that, when passed to Throttle::isWithinTimespanMs(value, interval), + // correctly reports whether the transmit was within interval ms. + return millis() - msAgo; +} + +bool TransmitHistory::saveToDisk() +{ + if (!dirty) { + return true; + } + + spiLock->lock(); + + FSCom.mkdir("/prefs"); + + // Remove old file first + if (FSCom.exists(FILENAME)) { + FSCom.remove(FILENAME); + } + + auto file = FSCom.open(FILENAME, FILE_O_WRITE); + if (file) { + FileHeader header{}; + header.magic = MAGIC; + header.version = VERSION; + header.count = (uint8_t)min((size_t)MAX_ENTRIES, history.size()); + + file.write((uint8_t *)&header, sizeof(header)); + + uint8_t written = 0; + for (auto &pair : history) { + if (written >= MAX_ENTRIES) + break; + Entry entry{}; + entry.key = pair.first; + entry.epochSeconds = pair.second; + file.write((uint8_t *)&entry, sizeof(entry)); + written++; + } + file.flush(); + file.close(); + LOG_DEBUG("TransmitHistory: saved %u entries to disk", written); + dirty = false; + spiLock->unlock(); + return true; + } else { + LOG_WARN("TransmitHistory: failed to open file for writing"); + } + + spiLock->unlock(); + return false; +} + +#else +// No filesystem available — provide stub with in-memory tracking +TransmitHistory *transmitHistory = nullptr; + +TransmitHistory *TransmitHistory::getInstance() +{ + if (!transmitHistory) { + transmitHistory = new TransmitHistory(); + } + return transmitHistory; +} + +void TransmitHistory::loadFromDisk() {} + +void TransmitHistory::setLastSentToMesh(uint16_t key) +{ + lastMillis[key] = millis(); +} + +uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const +{ + return 0; +} + +uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const +{ + auto mit = lastMillis.find(key); + return (mit != lastMillis.end()) ? mit->second : 0; +} + +bool TransmitHistory::saveToDisk() +{ + return true; +} + +#endif diff --git a/src/mesh/TransmitHistory.h b/src/mesh/TransmitHistory.h new file mode 100644 index 000000000..01201eaac --- /dev/null +++ b/src/mesh/TransmitHistory.h @@ -0,0 +1,88 @@ +#pragma once + +#include "configuration.h" +#include +#include + +/** + * TransmitHistory persists the last broadcast transmit time (epoch seconds) per portnum + * to the filesystem so that throttle checks survive reboots/crashes. + * + * On boot, modules call getLastSentToMeshMillis() to recover a millis()-relative timestamp + * from the stored epoch time, which plugs directly into existing throttle logic. + * + * On every broadcast transmit, modules call setLastSentToMesh() which updates the + * in-memory cache and flushes to disk. + * + * Keys are meshtastic_PortNum values (one entry per portnum). + */ + +#include "mesh/generated/meshtastic/portnums.pb.h" + +class TransmitHistory +{ + public: + static TransmitHistory *getInstance(); + + /** + * Load persisted transmit times from disk. Call once during init after filesystem is ready. + */ + void loadFromDisk(); + + /** + * Record that a broadcast was sent for the given key right now. + * Stores epoch seconds and flushes to disk. + */ + void setLastSentToMesh(uint16_t key); + + /** + * Get the last transmit epoch seconds for a given key, or 0 if unknown. + */ + uint32_t getLastSentToMeshEpoch(uint16_t key) const; + + /** + * Convert a stored epoch timestamp into a millis()-relative timestamp suitable + * for use with Throttle::isWithinTimespanMs(). + * + * Returns 0 if no valid time is stored or if the stored time is in the future + * (which shouldn't happen but guards against clock weirdness). + * + * Example: if the stored epoch is 300 seconds ago, and millis() is currently 10000, + * this returns 10000 - 300000 (wrapped appropriately for uint32_t arithmetic). + */ + uint32_t getLastSentToMeshMillis(uint16_t key) const; + + /** + * Flush dirty entries to disk. Called periodically or on demand. + * + * @return true if the data is persisted (or there was nothing to write), false on write/open failure. + */ + bool saveToDisk(); + + private: + TransmitHistory() = default; + + static constexpr const char *FILENAME = "/prefs/transmit_history.dat"; + static constexpr uint32_t MAGIC = 0x54485354; // "THST" + static constexpr uint8_t VERSION = 1; + static constexpr uint8_t MAX_ENTRIES = 16; + static constexpr uint32_t SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes + + struct __attribute__((packed)) Entry { + uint16_t key; + uint32_t epochSeconds; + }; + + struct __attribute__((packed)) FileHeader { + uint32_t magic; + uint8_t version; + uint8_t count; + }; + + std::map history; // key -> epoch seconds (for disk persistence) + std::map lastMillis; // key -> millis() value (for runtime throttle) + bool dirty = false; + uint32_t lastDiskSave = 0; // millis() of last disk flush +}; + +extern TransmitHistory *transmitHistory; diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index f947a5ac0..79bda557c 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -5,6 +5,7 @@ #include "NodeStatus.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "configuration.h" #include "main.h" #include @@ -133,11 +134,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() // Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient) uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline()); - if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) { + uint32_t lastNodeInfo = transmitHistory ? transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP) : 0; + if (!shorterTimeout && lastNodeInfo && Throttle::isWithinTimespanMs(lastNodeInfo, timeoutMs)) { LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000); ignoreRequest = true; // Mark it as ignored for MeshModule return NULL; - } else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) { + } else if (shorterTimeout && lastNodeInfo && Throttle::isWithinTimespanMs(lastNodeInfo, 60 * 1000)) { // For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago"); ignoreRequest = true; @@ -159,7 +161,8 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() strcpy(u.id, nodeDB->getNodeId().c_str()); LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); return allocDataProtobuf(u); } } diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h index d16fbeac2..0c0dec849 100644 --- a/src/modules/NodeInfoModule.h +++ b/src/modules/NodeInfoModule.h @@ -42,7 +42,6 @@ class NodeInfoModule : public ProtobufModule, private concurren virtual int32_t runOnce() override; private: - uint32_t lastSentToMesh = 0; // Last time we sent our NodeInfo to the mesh bool shorterTimeout = false; bool suppressReplyForCurrentRequest = false; std::map lastNodeInfoSeen; diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp index f7116e701..e9a21a22b 100644 --- a/src/modules/PositionModule.cpp +++ b/src/modules/PositionModule.cpp @@ -6,6 +6,7 @@ #include "NodeDB.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "TypeConversions.h" #include "airtime.h" #include "configuration.h" @@ -27,6 +28,15 @@ PositionModule::PositionModule() isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others nodeStatusObserver.observe(&nodeStatus->onNewStatus); + // Seed throttle timer from persisted transmit history so we don't re-broadcast immediately after reboot + if (transmitHistory) { + uint32_t restored = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP); + if (restored != 0) { + lastGpsSend = restored; + LOG_INFO("Position: restored lastGpsSend from transmit history"); + } + } + if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER && config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) { setIntervalFromNow(setStartDelay()); @@ -438,6 +448,8 @@ int32_t PositionModule::runOnce() lastGpsLatitude = node->position.latitude_i; lastGpsLongitude = node->position.longitude_i; + if (transmitHistory) + transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP); sendOurPosition(); if (config.device.role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) { sendLostAndFoundText(); diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 1e5567d7b..ca853d051 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -11,6 +11,7 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "UnitConversions.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" @@ -19,6 +20,8 @@ #include "sleep.h" #include +static constexpr uint16_t TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY = 0x8004; + // Sensors #include "Sensor/AddI2CSensorTemplate.h" #include "Sensor/PMSA003ISensor.h" @@ -108,11 +111,13 @@ int32_t AirQualityTelemetryModule::runOnce() // Wake up the sensors that need it LOG_INFO("Waking up sensors..."); + uint32_t lastTelemetry = + transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY) : 0; for (TelemetrySensor *sensor : sensors) { if (!sensor->canSleep()) { LOG_DEBUG("%s sensor doesn't have sleep feature. Skipping", sensor->sensorName); - } else if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh - sensor->wakeUpTimeMs(), + } else if (((lastTelemetry == 0) || + !Throttle::isWithinTimespanMs(lastTelemetry - sensor->wakeUpTimeMs(), Default::getConfiguredOrDefaultMsScaled( moduleConfig.telemetry.air_quality_interval, default_telemetry_broadcast_interval_secs, numOnlineNodes))) && @@ -131,14 +136,15 @@ int32_t AirQualityTelemetryModule::runOnce() } } - if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( - moduleConfig.telemetry.air_quality_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + if (((lastTelemetry == 0) || + !Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.air_quality_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index 9f19e396e..04936d8c1 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -68,7 +68,6 @@ class AirQualityTelemetryModule : private concurrency::OSThread, meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute // uint32_t sendToPhoneIntervalMs = 1000; // Send to phone every minute - uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; }; diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp index d09835f95..1c2d18c71 100644 --- a/src/modules/Telemetry/DeviceTelemetry.cpp +++ b/src/modules/Telemetry/DeviceTelemetry.cpp @@ -7,6 +7,7 @@ #include "RTC.h" #include "RadioLibInterface.h" #include "Router.h" +#include "TransmitHistory.h" #include "configuration.h" #include "main.h" #include "memGet.h" @@ -15,20 +16,23 @@ #include #define MAGIC_USB_BATTERY_LEVEL 101 +static constexpr uint16_t TX_HISTORY_KEY_DEVICE_TELEMETRY = 0x8001; int32_t DeviceTelemetryModule::runOnce() { refreshUptime(); + uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_DEVICE_TELEMETRY) : 0; bool isImpoliteRole = isSensorOrRouterRole(); - if (((lastSentToMesh == 0) || - ((uptimeLastMs - lastSentToMesh) >= - Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + if (((lastTelemetry == 0) || + ((uptimeLastMs - lastTelemetry) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, + default_telemetry_broadcast_interval_secs, + numOnlineNodes))) && airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() && config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN && moduleConfig.telemetry.device_telemetry_enabled) { sendTelemetry(); - lastSentToMesh = uptimeLastMs; + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_DEVICE_TELEMETRY); } else if (service->isToPhoneQueueEmpty()) { // Just send to phone when it's not our time to send to mesh yet // Only send while queue is empty (phone assumed connected) diff --git a/src/modules/Telemetry/DeviceTelemetry.h b/src/modules/Telemetry/DeviceTelemetry.h index 0dc431775..f37afee70 100644 --- a/src/modules/Telemetry/DeviceTelemetry.h +++ b/src/modules/Telemetry/DeviceTelemetry.h @@ -51,7 +51,6 @@ class DeviceTelemetryModule : private concurrency::OSThread, uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t sendStatsToPhoneIntervalMs = 15 * SECONDS_IN_MINUTE * 1000; // Send stats to phone every 15 minutes uint32_t lastSentStatsToPhone = 0; - uint32_t lastSentToMesh = 0; void refreshUptime() { diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 09f7be925..b7b6e04a9 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -10,6 +10,7 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "UnitConversions.h" #include "buzz.h" #include "graphics/SharedUIDisplay.h" @@ -145,6 +146,8 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "graphics/ScreenFonts.h" #include +static constexpr uint16_t TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY = 0x8002; + void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) { if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) { @@ -318,14 +321,17 @@ int32_t EnvironmentTelemetryModule::runOnce() } } - if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( - moduleConfig.telemetry.environment_update_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + uint32_t lastTelemetry = + transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY) : 0; + if (((lastTelemetry == 0) || + !Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h index fc80a986a..0b7e0f4cb 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.h +++ b/src/modules/Telemetry/EnvironmentTelemetry.h @@ -68,7 +68,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, bool firstTime = 1; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute - uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; }; diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index 9c57193cd..ae6b366bd 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -10,6 +10,7 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "UnitConversions.h" #include "main.h" #include "power.h" @@ -33,6 +34,8 @@ MLX90614Sensor mlx90614Sensor; #endif #include +static constexpr uint16_t TX_HISTORY_KEY_HEALTH_TELEMETRY = 0x8003; + int32_t HealthTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { @@ -69,14 +72,16 @@ int32_t HealthTelemetryModule::runOnce() return disable(); } - if (((lastSentToMesh == 0) || - !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( - moduleConfig.telemetry.health_update_interval, - default_telemetry_broadcast_interval_secs, numOnlineNodes))) && + uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_HEALTH_TELEMETRY) : 0; + if (((lastTelemetry == 0) || + !Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled( + moduleConfig.telemetry.health_update_interval, + default_telemetry_broadcast_interval_secs, numOnlineNodes))) && airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_HEALTH_TELEMETRY); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet diff --git a/src/modules/Telemetry/HealthTelemetry.h b/src/modules/Telemetry/HealthTelemetry.h index 1ab389fbf..4d0722201 100644 --- a/src/modules/Telemetry/HealthTelemetry.h +++ b/src/modules/Telemetry/HealthTelemetry.h @@ -55,7 +55,6 @@ class HealthTelemetryModule : private concurrency::OSThread, bool firstTime = 1; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute - uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; uint32_t sensor_read_error_count = 0; }; diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index 7147cb14a..d02aed9c2 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -10,6 +10,7 @@ #include "PowerTelemetry.h" #include "RTC.h" #include "Router.h" +#include "TransmitHistory.h" #include "graphics/SharedUIDisplay.h" #include "main.h" #include "power.h" @@ -22,6 +23,8 @@ #include "graphics/ScreenFonts.h" #include +static constexpr uint16_t TX_HISTORY_KEY_POWER_TELEMETRY = 0x8005; + namespace graphics { extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, @@ -88,10 +91,12 @@ int32_t PowerTelemetryModule::runOnce() if (!moduleConfig.telemetry.power_measurement_enabled) return disable(); - if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, sendToMeshIntervalMs)) && + uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_POWER_TELEMETRY) : 0; + if (((lastTelemetry == 0) || !Throttle::isWithinTimespanMs(lastTelemetry, sendToMeshIntervalMs)) && airTime->isTxAllowedAirUtil()) { sendTelemetry(); - lastSentToMesh = millis(); + if (transmitHistory) + transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_POWER_TELEMETRY); } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) && (service->isToPhoneQueueEmpty())) { // Just send to phone when it's not our time to send to mesh yet diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h index 97cefb4a5..134b40b6b 100644 --- a/src/modules/Telemetry/PowerTelemetry.h +++ b/src/modules/Telemetry/PowerTelemetry.h @@ -54,7 +54,6 @@ class PowerTelemetryModule : private concurrency::OSThread, bool firstTime = 1; meshtastic_MeshPacket *lastMeasurementPacket; uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute - uint32_t lastSentToMesh = 0; uint32_t lastSentToPhone = 0; uint32_t sensor_read_error_count = 0; }; diff --git a/src/sleep.cpp b/src/sleep.cpp index d42b9841a..8470e9273 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -9,6 +9,7 @@ #include "MeshService.h" #include "NodeDB.h" #include "PowerMon.h" +#include "TransmitHistory.h" #include "detect/LoRaRadioType.h" #include "error.h" #include "main.h" @@ -245,6 +246,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN nodeDB->saveToDisk(); } + // Persist broadcast transmit times so throttle survives reboot + if (transmitHistory) + transmitHistory->saveToDisk(); + #ifdef PIN_POWER_EN digitalWrite(PIN_POWER_EN, LOW); pinMode(PIN_POWER_EN, INPUT); // power off peripherals diff --git a/test/test_http_content_handler/test_main.cpp b/test/test_http_content_handler/test_main.cpp new file mode 100644 index 000000000..af0a41cef --- /dev/null +++ b/test/test_http_content_handler/test_main.cpp @@ -0,0 +1,19 @@ +#include "TestUtil.h" +#include + +static void test_placeholder() +{ + TEST_ASSERT_TRUE(true); +} + +extern "C" { +void setup() +{ + initializeTestEnvironment(); + UNITY_BEGIN(); + RUN_TEST(test_placeholder); + exit(UNITY_END()); +} + +void loop() {} +} diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp index a566dabf7..afc8a399a 100644 --- a/test/test_mqtt/MQTT.cpp +++ b/test/test_mqtt/MQTT.cpp @@ -334,7 +334,7 @@ void setUp(void) owner = meshtastic_User{.id = "!12345678"}; myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 0x12345678}; // Match the expected gateway ID in topic localPosition = - meshtastic_Position{.has_latitude_i = true, .latitude_i = 7 * 1e7, .has_longitude_i = true, .longitude_i = 3 * 1e7}; + meshtastic_Position{.has_latitude_i = true, .latitude_i = 700000000, .has_longitude_i = true, .longitude_i = 300000000}; router = mockRouter = new MockRouter(); service = mockMeshService = new MockMeshService(); diff --git a/test/test_transmit_history/test_main.cpp b/test/test_transmit_history/test_main.cpp new file mode 100644 index 000000000..992668d97 --- /dev/null +++ b/test/test_transmit_history/test_main.cpp @@ -0,0 +1,230 @@ +#include "TestUtil.h" +#include "TransmitHistory.h" +#include +#include + +// Reset the singleton between tests +static void resetTransmitHistory() +{ + if (transmitHistory) { + delete transmitHistory; + transmitHistory = nullptr; + } + transmitHistory = TransmitHistory::getInstance(); +} + +void setUp(void) +{ + resetTransmitHistory(); +} + +void tearDown(void) {} + +static void test_setLastSentToMesh_stores_millis() +{ + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + + uint32_t result = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + TEST_ASSERT_NOT_EQUAL(0, result); + + // The stored millis value should be very close to current millis() + uint32_t diff = millis() - result; + TEST_ASSERT_LESS_OR_EQUAL(100, diff); // Within 100ms +} + +static void test_set_overwrites_previous_value() +{ + transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP); + uint32_t first = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP); + + testDelay(50); + + transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP); + uint32_t second = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP); + + // The second value should be newer (larger millis) + TEST_ASSERT_GREATER_THAN(first, second); +} + +// --- Throttle integration --- + +static void test_throttle_blocks_within_interval() +{ + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + + // Should be within a 10-minute interval (just set it) + bool withinInterval = Throttle::isWithinTimespanMs(lastMs, 10 * 60 * 1000); + TEST_ASSERT_TRUE(withinInterval); +} + +static void test_throttle_allows_after_interval() +{ + // Unknown key returns 0 — throttle should NOT block + uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + TEST_ASSERT_EQUAL_UINT32(0, lastMs); + + // When lastMs == 0, the module check `lastMs == 0 || !isWithinTimespan` allows sending + bool shouldSend = (lastMs == 0) || !Throttle::isWithinTimespanMs(lastMs, 10 * 60 * 1000); + TEST_ASSERT_TRUE(shouldSend); +} + +static void test_throttle_blocks_after_set_then_zero_does_not() +{ + // Set it — now throttle should block + transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP); + uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP); + bool shouldSend = (lastMs == 0) || !Throttle::isWithinTimespanMs(lastMs, 60 * 60 * 1000); + TEST_ASSERT_FALSE(shouldSend); // Should be blocked (within 1hr interval) + + // Different key — should allow + uint32_t otherMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP); + bool otherShouldSend = (otherMs == 0) || !Throttle::isWithinTimespanMs(otherMs, 60 * 60 * 1000); + TEST_ASSERT_TRUE(otherShouldSend); +} + +// --- Multiple keys --- + +static void test_multiple_keys_stored_independently() +{ + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + uint32_t nodeInfoInitial = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + testDelay(20); + transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP); + uint32_t positionInitial = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP); + testDelay(20); + transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP); + + uint32_t nodeInfo = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + uint32_t position = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP); + uint32_t telemetry = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP); + + // All should be non-zero + TEST_ASSERT_NOT_EQUAL(0, nodeInfo); + TEST_ASSERT_NOT_EQUAL(0, position); + TEST_ASSERT_NOT_EQUAL(0, telemetry); + + // Updating other keys should not overwrite earlier key timestamps + TEST_ASSERT_EQUAL_UINT32(nodeInfoInitial, nodeInfo); + TEST_ASSERT_EQUAL_UINT32(positionInitial, position); +} + +// --- Singleton --- + +static void test_getInstance_returns_same_instance() +{ + TransmitHistory *a = TransmitHistory::getInstance(); + TransmitHistory *b = TransmitHistory::getInstance(); + TEST_ASSERT_EQUAL_PTR(a, b); +} + +static void test_getInstance_creates_global() +{ + if (transmitHistory) { + delete transmitHistory; + transmitHistory = nullptr; + } + TEST_ASSERT_NULL(transmitHistory); + + TransmitHistory::getInstance(); + TEST_ASSERT_NOT_NULL(transmitHistory); +} + +// --- Persistence round-trip (loadFromDisk / saveToDisk) --- + +static void test_save_and_load_round_trip() +{ + // Set some values + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + testDelay(10); + transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP); + + uint32_t nodeInfoEpoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP); + uint32_t positionEpoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_POSITION_APP); + + // Force save + transmitHistory->saveToDisk(); + + // Reset and reload + delete transmitHistory; + transmitHistory = nullptr; + transmitHistory = TransmitHistory::getInstance(); + transmitHistory->loadFromDisk(); + + // Epoch values should be restored (if RTC was available when set) + uint32_t restoredNodeInfo = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP); + uint32_t restoredPosition = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_POSITION_APP); + + TEST_ASSERT_EQUAL_UINT32(nodeInfoEpoch, restoredNodeInfo); + TEST_ASSERT_EQUAL_UINT32(positionEpoch, restoredPosition); + + // After loadFromDisk, millis should be seeded (non-zero) for stored entries + uint32_t restoredMillis = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + if (restoredNodeInfo > 0) { + // If epoch was stored, millis should be seeded from load + TEST_ASSERT_NOT_EQUAL(0, restoredMillis); + } +} + +// --- Boot without RTC scenario --- + +static void test_load_seeds_millis_even_without_rtc() +{ + // This tests the critical crash-reboot scenario: + // After loadFromDisk(), even if getTime() returns 0 (no RTC), + // lastMillis should be seeded so throttle blocks immediate re-broadcast. + + transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP); + transmitHistory->saveToDisk(); + + // Simulate reboot: destroy and recreate + delete transmitHistory; + transmitHistory = nullptr; + transmitHistory = TransmitHistory::getInstance(); + transmitHistory->loadFromDisk(); + + // The key insight: after load, getLastSentToMeshMillis should return non-zero + // because loadFromDisk seeds lastMillis[key] = millis() for every loaded entry. + // This ensures throttle works even without RTC. + uint32_t result = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP); + + uint32_t epoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP); + if (epoch > 0) { + // Data was persisted — millis must be seeded + TEST_ASSERT_NOT_EQUAL(0, result); + + // And it should cause throttle to block (treating as "just sent") + bool withinInterval = Throttle::isWithinTimespanMs(result, 10 * 60 * 1000); + TEST_ASSERT_TRUE(withinInterval); + } + // If epoch == 0, RTC wasn't available — no data was saved, so nothing to restore. + // This is expected on platforms without RTC during the very first boot. +} + +void setup() +{ + initializeTestEnvironment(); + + UNITY_BEGIN(); + + RUN_TEST(test_setLastSentToMesh_stores_millis); + RUN_TEST(test_set_overwrites_previous_value); + + RUN_TEST(test_throttle_blocks_within_interval); + RUN_TEST(test_throttle_allows_after_interval); + RUN_TEST(test_throttle_blocks_after_set_then_zero_does_not); + + RUN_TEST(test_multiple_keys_stored_independently); + + // Singleton + RUN_TEST(test_getInstance_returns_same_instance); + RUN_TEST(test_getInstance_creates_global); + + // Persistence + RUN_TEST(test_save_and_load_round_trip); + RUN_TEST(test_load_seeds_millis_even_without_rtc); + + exit(UNITY_END()); +} + +void loop() {} From efd68f9fadb24f934caabdb51932c15c21f7275c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 26 Feb 2026 07:38:13 -0600 Subject: [PATCH 207/223] Fix Bluetooth on RAK Ethernet Gateway by removing MESHTASTIC_EXCLUDE_POWER_FSM from build_flags (#9755) --- variants/nrf52840/rak4631_eth_gw/platformio.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/variants/nrf52840/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini index e06a271aa..d326edaab 100644 --- a/variants/nrf52840/rak4631_eth_gw/platformio.ini +++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini @@ -14,7 +14,6 @@ build_flags = ${nrf52840_base.build_flags} -DMESHTASTIC_EXCLUDE_WIFI=1 -DMESHTASTIC_EXCLUDE_SCREEN=1 ; -DMESHTASTIC_EXCLUDE_PKI=1 - -DMESHTASTIC_EXCLUDE_POWER_FSM=1 -DMESHTASTIC_EXCLUDE_POWERMON=1 ; -DMESHTASTIC_EXCLUDE_TZ=1 -DMESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1 From b5f98d12708d9ee853b07b54e5932f18f9c6b47d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 08:01:03 -0600 Subject: [PATCH 208/223] chore(deps): update adafruit_tsl2561 to v1.1.3 (#9757) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index e488d1e9c..096f8e9fb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -180,7 +180,7 @@ lib_deps = # renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150 dfrobot/DFRobot_BMM150@1.0.0 # renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561 - adafruit/Adafruit TSL2561@1.1.2 + adafruit/Adafruit TSL2561@1.1.3 # renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/library/BH1750_WE wollewald/BH1750_WE@1.1.10 From 2a99cc6a25b72390eba1eb09da1f9f411f38027b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 08:01:28 -0600 Subject: [PATCH 209/223] chore(deps): update adafruit mlx90614 to v2.1.6 (#9756) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 096f8e9fb..246bc2d28 100644 --- a/platformio.ini +++ b/platformio.ini @@ -158,7 +158,7 @@ lib_deps = # renovate: datasource=custom.pio depName=EmotiBit MLX90632 packageName=emotibit/library/EmotiBit MLX90632 emotibit/EmotiBit MLX90632@1.0.8 # renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library - adafruit/Adafruit MLX90614 Library@2.1.5 + adafruit/Adafruit MLX90614 Library@2.1.6 # renovate: datasource=git-refs depName=INA3221 packageName=https://github.com/sgtwilko/INA3221 gitBranch=FixOverflow https://github.com/sgtwilko/INA3221/archive/bb03d7e9bfcc74fc798838a54f4f99738f29fc6a.zip # renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass From 75cb8bb5ea2daf7fbd17775f2f3e011fc91d8aae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 08:09:44 -0600 Subject: [PATCH 210/223] chore(deps): update platformio/espressif32 to v6.13.0 (#9759) 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 bbbcd3cbe..159de8bc3 100644 --- a/variants/esp32/esp32-common.ini +++ b/variants/esp32/esp32-common.ini @@ -5,7 +5,7 @@ custom_esp32_kind = custom_mtjson_part = platform = # renovate: datasource=custom.pio depName=platformio/espressif32 packageName=platformio/platform/espressif32 - platformio/espressif32@6.12.0 + platformio/espressif32@6.13.0 extra_scripts = ${env.extra_scripts} From d34fa3e7dfde9687a0f3d8bd06bc6dd16fb2d222 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 08:11:00 -0600 Subject: [PATCH 211/223] chore(deps): update platformio/nordicnrf52 to v10.11.0 (#9760) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/nrf52840/nrf52.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/nrf52840/nrf52.ini b/variants/nrf52840/nrf52.ini index 727d2c741..aeda68a2e 100644 --- a/variants/nrf52840/nrf52.ini +++ b/variants/nrf52840/nrf52.ini @@ -2,7 +2,7 @@ ; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files platform = # renovate: datasource=custom.pio depName=platformio/nordicnrf52 packageName=platformio/platform/nordicnrf52 - platformio/nordicnrf52@10.10.0 + platformio/nordicnrf52@10.11.0 extends = arduino_base platform_packages = ; our custom Git version until they merge our PR From 83cac93ca8ff1099064b0597f62c66699d5f0402 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Fri, 27 Feb 2026 19:16:55 +0800 Subject: [PATCH 212/223] fix(MQTT): Send first MapReport as soon as possible (#8872) * fix(MQTT): First MapReport does not get sent Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs) is used to maintain the map reporting interval, but because last_report_to_map has an initial value of 0, the map report routine does not start until the system millis() time has passed map_publish_interval_msecs. Fix this by adding a check that last_report_to_map is not 0. Signed-off-by: Andrew Yong * feat(MQTT): Send MapReporting immediately upon location fix Do not update last_report_to_map when Map Report is attempted without a valid location, as this results in waiting up to an hour (or configured Map Report interval). That usually happens because most nodes do not keep GPS warm, so GPS usually locks after the first attempt at Map Report. This change also results in the log WARNing message getting spammed until a location is obtained, so remove the message for now. Signed-off-by: Andrew Yong * feat(MQTT): Throttled warning when position is not available for MapReport Signed-off-by: Andrew Yong --------- Signed-off-by: Andrew Yong --- src/mqtt/MQTT.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 2c10c4b2b..3e039a86b 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -53,6 +53,9 @@ static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for cha static bool isMqttServerAddressPrivate = false; static bool isConnected = false; +static uint32_t lastPositionUnavailableWarning = 0; +static const uint32_t POSITION_UNAVAILABLE_WARNING_INTERVAL_MS = 15000; // 15 seconds + inline void onReceiveProto(char *topic, byte *payload, size_t length) { const DecodedServiceEnvelope e(payload, length); @@ -845,12 +848,14 @@ void MQTT::perhapsReportToMap() map_position_precision = default_map_position_precision; } - if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs)) + if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs) && last_report_to_map != 0) return; if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) { - last_report_to_map = millis(); - LOG_WARN("MQTT Map report enabled, but no position available"); + if (Throttle::isWithinTimespanMs(lastPositionUnavailableWarning, POSITION_UNAVAILABLE_WARNING_INTERVAL_MS) == false) { + LOG_WARN("MQTT Map report enabled, but no position available"); + lastPositionUnavailableWarning = millis(); + } return; } From 857c7b3a3ad24638e9be1b367431d744a1b9f0f1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Fri, 27 Feb 2026 17:07:03 -0600 Subject: [PATCH 213/223] Don't launch canned message when waking screen or silencing notification (#9762) * Don't launch canned message when waking screen or silencing notification * Add screen ifdefs * Get the #if right --------- Co-authored-by: Ben Meadors --- src/input/InputBroker.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp index c0a56233f..acf79f149 100644 --- a/src/input/InputBroker.cpp +++ b/src/input/InputBroker.cpp @@ -100,13 +100,28 @@ void InputBroker::processInputEventQueue() int InputBroker::handleInputEvent(const InputEvent *event) { - powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release +#if HAS_SCREEN + bool screenWasOff = false; + if (screen) { + screenWasOff = !screen->isScreenOn(); + } +#endif + powerFSM.trigger(EVENT_INPUT); if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule && moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) { externalNotificationModule->stopNow(); + // If this turns off a notification, don't further process the event + return 0; } +#if HAS_SCREEN + if (screen && screenWasOff) { + // If the screen was off, it is in the process of turning on, and we just drop the event + return 0; + } +#endif + this->notifyObservers(event); return 0; } From c28bdbd7e6f3d372a6a208126feb4563950b3e52 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 28 Feb 2026 08:32:05 -0600 Subject: [PATCH 214/223] Fix mqtt test --- src/mqtt/MQTT.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 2c10c4b2b..28051bc89 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -453,7 +453,9 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE) enabled = true; runASAP = true; reconnectCount = 0; +#if !IS_RUNNING_TESTS publishNodeInfo(); +#endif } // preflightSleepObserver.observe(&preflightSleep); } else { From 43aa90fc3f2ad380202b44cbaecff4c837e596a2 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 28 Feb 2026 08:38:08 -0600 Subject: [PATCH 215/223] Unlock 0x8B5 register macro guard for SX162 (#9777) --- src/mesh/SX126xInterface.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index a9ee16545..553cd4ee5 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -194,16 +194,13 @@ template bool SX126xInterface::init() LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", result); } -#ifdef USE_GC1109_PA - // Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity - // on boards with the GC1109 FEM. Sets bit 0 of register 0x8B5. - // Reference: https://github.com/meshcore-dev/MeshCore/pull/1398 + // Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity. + // Sets bit 0 of register 0x8B5. if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) == RADIOLIB_ERR_NONE) { LOG_INFO("Applied SX1262 register 0x8B5 patch for GC1109 RX improvement"); } else { LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for GC1109"); } -#endif #if 0 // Read/write a register we are not using (only used for FSK mode) to test SPI comms From fc0ad4956ed2e971325ab6f5c930f482ceb52b38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:40:15 -0600 Subject: [PATCH 216/223] chore(deps): update adafruit ahtx0 to v2.0.6 (#9766) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 246bc2d28..a334a8b96 100644 --- a/platformio.ini +++ b/platformio.ini @@ -150,7 +150,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH adafruit/Adafruit LIS3DH@1.3.0 # renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0 - adafruit/Adafruit AHTX0@2.0.5 + adafruit/Adafruit AHTX0@2.0.6 # renovate: datasource=custom.pio depName=Adafruit LSM6DS packageName=adafruit/library/Adafruit LSM6DS adafruit/Adafruit LSM6DS@4.7.4 # renovate: datasource=custom.pio depName=Adafruit TSL2591 packageName=adafruit/library/Adafruit TSL2591 Library From 60c2592694b4b32c52f1f121d2d97fe07f159f9b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:41:01 -0600 Subject: [PATCH 217/223] chore(deps): update adafruit dps310 to v1.1.6 (#9763) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index a334a8b96..c82cfd11c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -138,7 +138,7 @@ lib_deps = # renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library adafruit/Adafruit BME280 Library@2.3.0 # renovate: datasource=custom.pio depName=Adafruit DPS310 packageName=adafruit/library/Adafruit DPS310 - adafruit/Adafruit DPS310@1.1.5 + adafruit/Adafruit DPS310@1.1.6 # renovate: datasource=custom.pio depName=Adafruit MCP9808 packageName=adafruit/library/Adafruit MCP9808 Library adafruit/Adafruit MCP9808 Library@2.0.2 # renovate: datasource=custom.pio depName=Adafruit INA260 packageName=adafruit/library/Adafruit INA260 Library From 1e16185fdd58962f9e32ca61d84945a33b0e16eb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:41:21 -0600 Subject: [PATCH 218/223] chore(deps): update platformio/ststm32 to v19.5.0 (#9764) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- variants/stm32/stm32.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/stm32/stm32.ini b/variants/stm32/stm32.ini index bb0a4d3ce..7a3b37642 100644 --- a/variants/stm32/stm32.ini +++ b/variants/stm32/stm32.ini @@ -2,7 +2,7 @@ extends = arduino_base platform = # renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32 - platformio/ststm32@19.4.0 + platformio/ststm32@19.5.0 platform_packages = # renovate: datasource=github-tags depName=Arduino_Core_STM32 packageName=stm32duino/Arduino_Core_STM32 platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip From 2b7a230977cd06aaec6703179c9d009192ebfee7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 08:41:34 -0600 Subject: [PATCH 219/223] Upgrade trunk (#9752) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index cdd08cfaf..f489651a9 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,14 +9,14 @@ plugins: lint: enabled: - checkov@3.2.506 - - renovate@43.35.1 + - renovate@43.43.2 - prettier@3.8.1 - - trufflehog@3.93.4 + - trufflehog@3.93.5 - yamllint@1.38.0 - bandit@1.9.4 - trivy@0.69.1 - taplo@0.10.0 - - ruff@0.15.2 + - ruff@0.15.4 - isort@8.0.0 - markdownlint@0.47.0 - oxipng@10.1.0 From 6920a5f8b1b4f183c36732c4394c16125c7b3976 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 28 Feb 2026 09:34:26 -0600 Subject: [PATCH 220/223] Increase PSRAM malloc threshold from 256 bytes to 2048 bytes (#9758) --- src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 4c686937b..8a46b3f5b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -389,8 +389,8 @@ void setup() #if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM) #ifndef SENSECAP_INDICATOR - // use PSRAM for malloc calls > 256 bytes - heap_caps_malloc_extmem_enable(256); + // use PSRAM for malloc calls > 2048 bytes + heap_caps_malloc_extmem_enable(2048); #endif #endif From 563adc6aaa7bb0b901e7d902fc2aac617f493495 Mon Sep 17 00:00:00 2001 From: m1nl Date: Sun, 1 Mar 2026 00:17:29 +0100 Subject: [PATCH 221/223] enhancement(mesh): remove late packets from tx queue when full (#9779) * enhance tx queue priority management In busy environments, especially for ROUTER_LATE role, tx queue fills very quickly. Delayed packets became late but new packets to be retransmitted won't be put into the tx queue as old ones stay there for a very long time (even a minute or more). This change makes meshtastic prioritize new packets over late packets from tx queue and allows to remove late packet from back of tx queue when there is no space for a new one. * apply copilot recommendation for cast --------- Co-authored-by: Ben Meadors --- src/mesh/MeshPacketQueue.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp index cbea85c62..4aad40c69 100644 --- a/src/mesh/MeshPacketQueue.cpp +++ b/src/mesh/MeshPacketQueue.cpp @@ -184,6 +184,29 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p) } } + if (backPacket->tx_after) { + // Check if there's a late packet at the queue end + auto now = millis(); + if (backPacket->tx_after < now && (!p->tx_after || backPacket->tx_after > p->tx_after)) { + int32_t dt = (int32_t)(backPacket->tx_after - now); + if (p->tx_after) { + LOG_WARN("Dropping late packet 0x%08x with TX delay %dms to make room in the TX queue for packet 0x%08x with " + "TX delay %ums", + backPacket->id, dt, p->id, p->tx_after - now); + + } else { + LOG_WARN("Dropping late packet 0x%08x with TX delay %dms to make room in the TX queue for packet 0x%08x " + "with no TX delay", + backPacket->id, dt, p->id); + } + queue.pop_back(); + packetPool.release(backPacket); + // Insert the new packet in the correct order + enqueue(p); + return true; + } + } + // If the back packet's priority is not lower, no replacement occurs return false; } \ No newline at end of file From 80af726877abc3656e1c3289c0d0695f00ec581b Mon Sep 17 00:00:00 2001 From: m1nl Date: Sun, 1 Mar 2026 01:37:41 +0100 Subject: [PATCH 222/223] avoid memory leak when possibly malformed packet received (#9781) getByIndex allocates memory and returns dummy channel whenever chIndex validation fails. Comment implies this may happen when malformed packet is received. The fix changes implementation so static dummyChannel is returned in such case. Co-authored-by: Ben Meadors --- src/mesh/Channels.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 4dcd94e3b..1583567fe 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -22,6 +22,8 @@ const char *Channels::serialChannel = "serial"; const char *Channels::mqttChannel = "mqtt"; #endif +meshtastic_Channel dummyChannel = {.index = -1}; + uint8_t xorHash(const uint8_t *p, size_t len) { uint8_t code = 0; @@ -309,13 +311,7 @@ meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex) return *ch; } else { LOG_ERROR("Invalid channel index %d > %d, malformed packet received?", chIndex, channelFile.channels_count); - - static meshtastic_Channel *ch = (meshtastic_Channel *)malloc(sizeof(meshtastic_Channel)); - memset(ch, 0, sizeof(meshtastic_Channel)); - // ch.index -1 means we don't know the channel locally and need to look it up by settings.name - // not sure this is handled right everywhere - ch->index = -1; - return *ch; + return dummyChannel; } } From 8093e2ed5a63da256f260f0b4b7cb1dab62b6b98 Mon Sep 17 00:00:00 2001 From: Clive Blackledge Date: Mon, 2 Mar 2026 04:45:42 -0800 Subject: [PATCH 223/223] Bug: Mqtt fix testcase due to immediately sending MapReport (#8872) (#9784) * Add ESP32 Power Management lessons learned document Documents our experimentation with ESP-IDF DFS and why it doesn't work well for Meshtastic (RTOS locks, BLE locks, USB issues). Proposes simpler alternative: manual setCpuFrequencyMhz() control with explicit triggers for when to go fast vs slow. * Added a lambda function to clear startup output in the MQTT unit test to ensure a clean state before and after the MQTT subscription process. --- test/test_mqtt/MQTT.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp index afc8a399a..4a2eed87d 100644 --- a/test/test_mqtt/MQTT.cpp +++ b/test/test_mqtt/MQTT.cpp @@ -289,13 +289,23 @@ class MQTTUnitTest : public MQTT mqtt = unitTest = new MQTTUnitTest(); mqtt->start(); + auto clearStartupOutput = []() { + pubsub->published_.clear(); + if (mockMeshService != nullptr) { + mockMeshService->messages_.clear(); + mockMeshService->notifications_.clear(); + } + }; + if (!moduleConfig.mqtt.enabled || moduleConfig.mqtt.proxy_to_client_enabled || *moduleConfig.mqtt.root) { loopUntil([] { return true; }); // Loop once + clearStartupOutput(); return; } // Wait for MQTT to subscribe to all topics. TEST_ASSERT_TRUE(loopUntil( [] { return pubsub->subscriptions_.count("msh/2/e/test/+") && pubsub->subscriptions_.count("msh/2/e/PKI/+"); })); + clearStartupOutput(); } PubSubClient &getPubSub() { return pubSub; } }; @@ -930,4 +940,4 @@ void setup() UNITY_END(); } #endif -void loop() {} \ No newline at end of file +void loop() {}