From 5ae4ff9162c4c9d90bc33b249b5f28a36d263d40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 13:59:40 -0500 Subject: [PATCH 01/81] Upgrade trunk (#7763) 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 a0dcf2ff5..c57c16319 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.465 - - renovate@41.82.10 + - checkov@3.2.467 + - renovate@41.88.0 - prettier@3.6.2 - trufflehog@3.90.5 - yamllint@1.37.1 From 44688e83630fcfcff94184f7a47de2faa5634bbb Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 1 Sep 2025 14:16:24 +1000 Subject: [PATCH 02/81] Fix device-install.bat baud rate (#7486) As reported by @gruberaaron , work to improve the 1200bps reset for esptool caused all runs of device-install.bat to use 1200bps as the baud rate. This change removes the general SET "ESPTOOL_BAUD=1200" that was causing the issues and places the baud settings for reset mode inside the conditional. Fixes https://github.com/meshtastic/firmware/issues/7172 --- bin/device-install.bat | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/device-install.bat b/bin/device-install.bat index 12bfd4f6e..93b2fcec1 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -100,7 +100,6 @@ IF NOT "!FILENAME:update=!"=="!FILENAME!" ( ) :skip-filename -SET "ESPTOOL_BAUD=1200" CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..." IF NOT "__%PYTHON%__"=="____" ( @@ -142,7 +141,7 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!." IF %BPS_RESET% EQU 1 ( @REM Attempt to change mode via 1200bps Reset. - CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status + CALL :RUN_ESPTOOL 1200 --after no_reset read_flash_status GOTO eof ) From edeb25cab5ac6c3997b37a1eff5770b422e2d063 Mon Sep 17 00:00:00 2001 From: Onyx Clawe <58921814+OnyxClawe@users.noreply.github.com> Date: Mon, 1 Sep 2025 05:57:15 -0700 Subject: [PATCH 03/81] Update variant.h (#7520) Updated ADC, Full charge now results in 100% charge being reported instead of 95% charge Co-authored-by: OnyxtheDragon <58921814+OnyxtheDragon@users.noreply.github.com> --- variants/nrf52840/heltec_mesh_node_t114/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index f4f0baf13..b71106a53 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -208,7 +208,7 @@ No longer populated on PCB #undef AREF_VOLTAGE #define AREF_VOLTAGE 3.0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0 -#define ADC_MULTIPLIER (4.90F) +#define ADC_MULTIPLIER (4.99F) #define HAS_RTC 0 #ifdef __cplusplus From 6b94c297b93b0d192653785fbcd61e970d4b1194 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:57:49 +0200 Subject: [PATCH 04/81] fix: T-LoRa Pager / T-Deck Pro shutdown (#7792) * power down during LS and shutdown * fix T-Deck Pro shutdown * use device specific define * slightly rephrase the power off display message --- src/Power.cpp | 48 +++++++++++++++++++++++++----------------------- src/sleep.cpp | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index bf74f6e53..a123fe984 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -128,6 +128,7 @@ RAK9154Sensor rak9154Sensor; #ifdef HAS_PPM // note: XPOWERS_CHIP_XXX must be defined in variant.h #include +XPowersPPM *PPM = NULL; #endif #ifdef HAS_BQ27220 @@ -681,7 +682,7 @@ bool Power::setup() found = true; } else if (lipoChargerInit()) { found = true; - } else if (meshSolarInit()) { + } else if (meshSolarInit()) { found = true; } else if (analogInit()) { found = true; @@ -745,7 +746,11 @@ 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 +#else screen->showSimpleBanner("Shutting Down...", 0); // stays on screen +#endif } #endif #if !defined(ARCH_STM32WL) @@ -763,7 +768,7 @@ void Power::shutdown() #ifdef PIN_LED3 ledOff(PIN_LED3); #endif - doDeepSleep(DELAY_FOREVER, false, true); + doDeepSleep(DELAY_FOREVER, true, true); #elif defined(ARCH_PORTDUINO) exit(EXIT_SUCCESS); #else @@ -1320,7 +1325,6 @@ bool Power::lipoInit() class LipoCharger : public HasBatteryLevel { private: - XPowersPPM *ppm = nullptr; BQ27220 *bq = nullptr; public: @@ -1329,41 +1333,41 @@ class LipoCharger : public HasBatteryLevel */ bool runOnce() { - if (ppm == nullptr) { - ppm = new XPowersPPM; - bool result = ppm->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR); + if (PPM == nullptr) { + PPM = new XPowersPPM; + 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); + // PPM->setSysPowerDownVoltage(3100); // Set input current limit, default is 500mA - // ppm->setInputCurrentLimit(800); + // PPM->setInputCurrentLimit(800); // Disable current limit pin - // ppm->disableCurrentLimitPin(); + // PPM->disableCurrentLimitPin(); // Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV - ppm->setChargeTargetVoltage(4288); + PPM->setChargeTargetVoltage(4288); // Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA - // ppm->setPrechargeCurr(64); + // PPM->setPrechargeCurr(64); // The premise is that limit pin is disabled, or it will // only follow the maximum charging current set by limit pin. // Set the charging current , Range:0~5056mA ,step:64mA - ppm->setChargerConstantCurr(1024); + PPM->setChargerConstantCurr(1024); // To obtain voltage data, the ADC must be enabled first - ppm->enableMeasure(); + PPM->enableMeasure(); // Turn on charging function // If there is no battery connected, do not turn on the charging function - ppm->enableCharge(); + PPM->enableCharge(); } else { LOG_WARN("PPM BQ25896 init failed"); - delete ppm; - ppm = nullptr; + delete PPM; + PPM = nullptr; return false; } } @@ -1404,23 +1408,23 @@ class LipoCharger : public HasBatteryLevel /** * return true if there is a battery installed in this unit */ - virtual bool isBatteryConnect() override { return ppm->getBattVoltage() > 0; } + virtual bool isBatteryConnect() override { return PPM->getBattVoltage() > 0; } /** * return true if there is an external power source detected */ - virtual bool isVbusIn() override { return ppm->getVbusVoltage() > 0; } + virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; } /** * return true if the battery is currently charging */ virtual bool isCharging() override { - bool isCharging = ppm->isCharging(); + bool isCharging = PPM->isCharging(); if (isCharging) { LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull()); } else { - if (!ppm->isVbusIn()) { + if (!PPM->isVbusIn()) { LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity()); } } @@ -1453,8 +1457,6 @@ bool Power::lipoChargerInit() } #endif - - #ifdef HELTEC_MESH_SOLAR #include "meshSolarApp.h" @@ -1492,7 +1494,7 @@ class meshSolarBatteryLevel : public HasBatteryLevel /** * return true if there is an external power source detected */ - virtual bool isVbusIn() override { return meshSolarIsVbusIn();} + virtual bool isVbusIn() override { return meshSolarIsVbusIn(); } /** * return true if the battery is currently charging diff --git a/src/sleep.cpp b/src/sleep.cpp index 1a5f246c5..bff318900 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -32,6 +32,16 @@ esp_sleep_source_t wakeCause; // the reason we booted this time #endif #include "Throttle.h" +#ifdef USE_XL9555 +#include "ExtensionIOXL9555.hpp" +extern ExtensionIOXL9555 io; +#endif + +#ifdef HAS_PPM +#include +extern XPowersPPM *PPM; +#endif + #ifndef INCLUDE_vTaskSuspend #define INCLUDE_vTaskSuspend 0 #endif @@ -297,6 +307,14 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN #endif #endif +#ifdef HAS_PPM + if (PPM) { + LOG_INFO("PMM shutdown"); + console->flush(); + PPM->shutdown(); + } +#endif + #ifdef HAS_PMU if (pmu_found && PMU) { // Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep. @@ -412,6 +430,16 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r if (pmu_found) gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq #endif + +#ifdef T_LORA_PAGER + LOG_DEBUG("power down XL9555 io"); + io.digitalWrite(EXPANDS_DRV_EN, LOW); + io.digitalWrite(EXPANDS_AMP_EN, LOW); + io.digitalWrite(EXPANDS_KB_EN, LOW); + io.digitalWrite(EXPANDS_SD_EN, LOW); + io.digitalWrite(EXPANDS_GPIO_EN, LOW); +#endif + auto res = esp_sleep_enable_gpio_wakeup(); if (res != ESP_OK) { LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res); @@ -452,6 +480,14 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r gpio_wakeup_disable((gpio_num_t)RF95_IRQ); } #endif +#ifdef T_LORA_PAGER + LOG_DEBUG("power up XL9555 io"); + io.digitalWrite(EXPANDS_DRV_EN, HIGH); + io.digitalWrite(EXPANDS_AMP_EN, HIGH); + io.digitalWrite(EXPANDS_KB_EN, HIGH); + io.digitalWrite(EXPANDS_SD_EN, HIGH); + io.digitalWrite(EXPANDS_GPIO_EN, HIGH); +#endif esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here From 5f7eec5504cf047674ee6b9363e200fa18573019 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 07:58:01 -0500 Subject: [PATCH 05/81] Upgrade trunk (#7804) 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 c57c16319..651e25b2a 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,14 +9,14 @@ plugins: lint: enabled: - checkov@3.2.467 - - renovate@41.88.0 + - renovate@41.90.1 - prettier@3.6.2 - trufflehog@3.90.5 - yamllint@1.37.1 - bandit@1.8.6 - trivy@0.65.0 - taplo@0.10.0 - - ruff@0.12.10 + - ruff@0.12.11 - isort@6.0.1 - markdownlint@0.45.0 - oxipng@9.1.5 From fddc4e00cab177253cc5e3d63fc9f65704e111a7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:27:14 -0500 Subject: [PATCH 06/81] Automated version bumps (#7790) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 7 +++++-- version.properties | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index 1d97e2a66..bebbc285e 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.8 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.7 diff --git a/debian/changelog b/debian/changelog index ff59db89e..3bb0de79c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -meshtasticd (2.7.7.0) UNRELEASED; urgency=medium +meshtasticd (2.7.8.0) UNRELEASED; urgency=medium [ Austin Lane ] * Initial packaging @@ -41,4 +41,7 @@ meshtasticd (2.7.7.0) UNRELEASED; urgency=medium * GitHub Actions Automatic version bump * GitHub Actions Automatic version bump - -- Ubuntu Thu, 28 Aug 2025 10:33:25 +0000 + [ ] + * GitHub Actions Automatic version bump + + -- Sat, 30 Aug 2025 00:26:04 +0000 diff --git a/version.properties b/version.properties index 2e5193d49..506675fa8 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 7 +build = 8 From bd3cbfc1adda753ae1f7ae1ef0ec45fc6b3ee5b8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 31 Aug 2025 21:08:58 -0500 Subject: [PATCH 07/81] Add support for the RV-3028 on native Linux (#7802) --- src/configuration.h | 4 ++-- src/gps/RTC.cpp | 8 ++++---- src/main.cpp | 2 +- variants/native/portduino/platformio.ini | 5 ++++- variants/native/portduino/variant.h | 5 ++++- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 8b4fd82c7..81632c89e 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -26,10 +26,10 @@ along with this program. If not, see . #include -#ifdef RV3028_RTC +#if __has_include("Melopero_RV3028.h") #include "Melopero_RV3028.h" #endif -#ifdef PCF8563_RTC +#if __has_include("pcf8563.h") #include "pcf8563.h" #endif diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index 185adacd9..ceb79eebf 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -55,9 +55,9 @@ RTCSetResult readFromRTC() LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch); - timeStartMsec = now; - zeroOffsetSecs = tv.tv_sec; if (currentQuality == RTCQualityNone) { + timeStartMsec = now; + zeroOffsetSecs = tv.tv_sec; currentQuality = RTCQualityDevice; } return RTCSetResultSuccess; @@ -94,9 +94,9 @@ RTCSetResult readFromRTC() LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch); - timeStartMsec = now; - zeroOffsetSecs = tv.tv_sec; if (currentQuality == RTCQualityNone) { + timeStartMsec = now; + zeroOffsetSecs = tv.tv_sec; currentQuality = RTCQualityDevice; } return RTCSetResultSuccess; diff --git a/src/main.cpp b/src/main.cpp index 338487914..111709d07 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -419,7 +419,7 @@ void setup() struct timeval tv; tv.tv_sec = time(NULL); tv.tv_usec = 0; - perhapsSetRTC(RTCQualityNTP, &tv); + perhapsSetRTC(RTCQualityDevice, &tv); #endif powerMonInit(); diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini index 62942a80e..c47ab8bf1 100644 --- a/variants/native/portduino/platformio.ini +++ b/variants/native/portduino/platformio.ini @@ -3,7 +3,10 @@ extends = portduino_base build_flags = ${portduino_base.build_flags} -I variants/native/portduino -I /usr/include board = cross_platform -lib_deps = ${portduino_base.lib_deps} +lib_deps = + ${portduino_base.lib_deps} + melopero/Melopero RV3028@^1.1.0 + build_src_filter = ${portduino_base.build_src_filter} [env:native] diff --git a/variants/native/portduino/variant.h b/variants/native/portduino/variant.h index ce7dbd865..a7ca865be 100644 --- a/variants/native/portduino/variant.h +++ b/variants/native/portduino/variant.h @@ -4,4 +4,7 @@ #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 #define MAX_RX_TOPHONE settingsMap[maxtophone] -#define MAX_NUM_NODES settingsMap[maxnodes] \ No newline at end of file +#define MAX_NUM_NODES settingsMap[maxnodes] + +// RAK12002 RTC Module +#define RV3028_RTC (uint8_t)0b1010010 \ No newline at end of file From 7d1300ab66c4107f13e78769ab258760241d3331 Mon Sep 17 00:00:00 2001 From: Wilson Date: Tue, 2 Sep 2025 13:06:24 +0800 Subject: [PATCH 08/81] Add gat562_mesh_tracker_pro device. (#7815) --- boards/gat562_mesh_tracker_pro.json | 52 +++ .../gat562_mesh_tracker_pro/platformio.ini | 15 + .../gat562_mesh_tracker_pro/variant.cpp | 54 ++++ .../gat562_mesh_tracker_pro/variant.h | 300 ++++++++++++++++++ 4 files changed, 421 insertions(+) create mode 100644 boards/gat562_mesh_tracker_pro.json create mode 100644 variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini create mode 100644 variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp create mode 100644 variants/nrf52840/gat562_mesh_tracker_pro/variant.h diff --git a/boards/gat562_mesh_tracker_pro.json b/boards/gat562_mesh_tracker_pro.json new file mode 100644 index 000000000..92e5feb89 --- /dev/null +++ b/boards/gat562_mesh_tracker_pro.json @@ -0,0 +1,52 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"] + ], + "usb_product": "GAT562 Mesh Tracker Pro", + "mcu": "nrf52840", + "variant": "gat562_mesh_tracker_pro", + "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", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino", "freertos"], + "name": "GAT562 Mesh Tracker Pro", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "http://www.gat-iot.com/", + "vendor": "GAT" +} diff --git a/variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini b/variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini new file mode 100644 index 000000000..8052d6336 --- /dev/null +++ b/variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini @@ -0,0 +1,15 @@ +; GAT562 Mesh Tracker Pro with Trackball support +[env:gat562_mesh_tracker_pro] +extends = nrf52840_base +board = gat562_mesh_tracker_pro +board_check = true +build_flags = ${nrf52840_base.build_flags} + -I variants/nrf52840/gat562_mesh_tracker_pro + -D GAT562_MESH_TRACKER_PRO + -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -DRADIOLIB_EXCLUDE_SX128X=1 + -DRADIOLIB_EXCLUDE_SX127X=1 + -DRADIOLIB_EXCLUDE_LR11X0=1 +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/gat562_mesh_tracker_pro> +lib_deps = + ${nrf52840_base.lib_deps} diff --git a/variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp b/variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp new file mode 100644 index 000000000..0b9a41025 --- /dev/null +++ b/variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp @@ -0,0 +1,54 @@ +/* + 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() +{ + // LED1 & LED2 + 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); + +// Initialize trackball pins as inputs with pullup +#ifdef HAS_TRACKBALL + pinMode(TB_UP, INPUT_PULLUP); + pinMode(TB_DOWN, INPUT_PULLUP); + pinMode(TB_LEFT, INPUT_PULLUP); + pinMode(TB_RIGHT, INPUT_PULLUP); + pinMode(TB_PRESS, INPUT_PULLUP); +#endif +} diff --git a/variants/nrf52840/gat562_mesh_tracker_pro/variant.h b/variants/nrf52840/gat562_mesh_tracker_pro/variant.h new file mode 100644 index 000000000..367e0c491 --- /dev/null +++ b/variants/nrf52840/gat562_mesh_tracker_pro/variant.h @@ -0,0 +1,300 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_GAT562_MESH_TRACKER_PRO_ +#define _VARIANT_GAT562_MESH_TRACKER_PRO_ + +// led pin 2 (blue), see https://github.com/meshtastic/firmware/blob/master/src/mesh/NodeDB.cpp#L723 +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ + +#define CANCEL_BUTTON_PIN 9 +#define BUTTON_NEED_PULLUP +#define CANCEL_BUTTON_ACTIVE_LOW true +#define CANCEL_BUTTON_ACTIVE_PULLUP false + +/* + * Analog pins + */ +#define PIN_A0 (5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * eink display pins + */ + +// #define PIN_EINK_CS (0 + 26) +// #define PIN_EINK_BUSY (0 + 4) +// #define PIN_EINK_DC (0 + 17) +// #define PIN_EINK_RES (-1) +// #define PIN_EINK_SCLK (0 + 3) +// #define PIN_EINK_MOSI (0 + 30) // also called SDI + +// #define USE_EINK + +// Display - OLED connected via I2C +#define HAS_SCREEN 1 +#define USE_SSD1306 + +// RAKRGB +// #define HAS_NCP5623 + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) + +// QSPI Pins +#define PIN_QSPI_SCK 3 +#define PIN_QSPI_CS 26 +#define PIN_QSPI_IO0 30 +#define PIN_QSPI_IO1 29 +#define PIN_QSPI_IO2 28 +#define PIN_QSPI_IO3 2 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES IS25LP080D +#define EXTERNAL_FLASH_USE_QSPI + +/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports + RAK5005-O <-> nRF52840 + IO1 <-> P0.17 (Arduino GPIO number 17) + IO2 <-> P1.02 (Arduino GPIO number 34) + IO3 <-> P0.21 (Arduino GPIO number 21) + IO4 <-> P0.04 (Arduino GPIO number 4) + IO5 <-> P0.09 (Arduino GPIO number 9) + IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) + SW1 <-> P0.01 (Arduino GPIO number 1) + A0 <-> P0.04/AIN2 (Arduino Analog A2 + A1 <-> P0.31/AIN7 (Arduino Analog A7 + SPI_CS <-> P0.26 (Arduino GPIO number 26) + */ + +// RAK4630 LoRa module + +/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) + +P1.10 NSS SPI NSS (Arduino GPIO number 42) +P1.11 SCK SPI CLK (Arduino GPIO number 43) +P1.12 MOSI SPI MOSI (Arduino GPIO number 44) +P1.13 MISO SPI MISO (Arduino GPIO number 45) +P1.14 BUSY BUSY signal (Arduino GPIO number 46) +P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) +P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) + +Important for successful SX1262 initialization: + +* Setup DIO2 to control the antenna switch +* Setup DIO3 to control the TCXO power supply +* Setup the SX1262 to use it's DCDC regulator and not the LDO +* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the +control of the antenna switch + +SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG + +*/ + +// configure the SET pin on the RAK12039 sensor board to disable the sensor while not reading +// air quality telemetry. PIN_NFC2 doesn't seem to be used anywhere else in the codebase, but if +// you're having problems with your node behaving weirdly when a RAK12039 board isn't connected, +// try disabling this. +// #define PMSA003I_ENABLE_PIN PIN_NFC2 + +// #define DETECTION_SENSOR_EN 4 + +#define USE_SX1262 +#define SX126X_CS (42) +#define SX126X_DIO1 (47) +#define SX126X_BUSY (46) +#define SX126X_RESET (38) +// #define SX126X_TXEN (39) +// #define SX126X_RXEN (37) +#define SX126X_POWER_EN (37) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Testing USB detection +#define NRF_APM + +// enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings +#define PIN_3V3_EN (34) + +// RAK1910 GPS module +// If using the wisblock GPS module and pluged into Port A on WisBlock base +// IO1 is hooked to PPS (pin 12 on header) = gpio 17 +// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). +// Therefore must be 1 to keep peripherals powered +// Power is on the controllable 3V3_S rail +// #define PIN_GPS_RESET (34) +// #define PIN_GPS_EN PIN_3V3_EN +#define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +#define GPS_BAUDRATE 9600 + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press + +// RAK12002 RTC Module +// #define RV3028_RTC (uint8_t)0b1010010 + +// RAK18001 Buzzer in Slot C +// #define PIN_BUZZER 21 // IO3 is PWM2 +// NEW: set this via protobuf instead! + +// Battery +// The battery sense is hooked to pin A0 (5) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#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 + +// #define PIN_ETHERNET_RESET 21 +// #define PIN_ETHERNET_SS PIN_EINK_CS +// #define ETH_SPI_PORT SPI1 +// #define AQ_SET_PIN 10 + +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// Trackball Configuration +// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +#define CANNED_MESSAGE_MODULE_ENABLE 1 +#define CANNED_MESSAGE_ADD_CONFIRMATION 1 + +// Trackball pins +#define HAS_TRACKBALL 1 +#define TB_LEFT 30 // P0.30 +#define TB_DOWN 4 // P0.04 +#define TB_RIGHT 31 // P0.31 +#define TB_UP 28 // P0.28 +#define TB_PRESS 26 // P0.26 (SELECT) +#define TB_DIRECTION FALLING + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From cfc1bf10c9db958df5aa01e6826c109d66f3e3e7 Mon Sep 17 00:00:00 2001 From: Jason P Date: Tue, 2 Sep 2025 05:55:57 -0500 Subject: [PATCH 09/81] If usePreset is False, show value as Custom (#7812) --- src/DisplayFormatters.cpp | 9 ++++++++- src/DisplayFormatters.h | 3 ++- src/graphics/draw/DebugRenderer.cpp | 8 +------- src/mesh/Channels.cpp | 5 +++-- src/mesh/RadioInterface.cpp | 3 ++- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp index 44bc0897b..d367aa661 100644 --- a/src/DisplayFormatters.cpp +++ b/src/DisplayFormatters.cpp @@ -1,7 +1,14 @@ #include "DisplayFormatters.h" -const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName) +const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName, + bool usePreset) { + + // If use_preset is false, always return "Custom" + if (!usePreset) { + return "Custom"; + } + switch (preset) { case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: return useShortName ? "ShortT" : "ShortTurbo"; diff --git a/src/DisplayFormatters.h b/src/DisplayFormatters.h index f8ccfcbb6..2d7a3e8db 100644 --- a/src/DisplayFormatters.h +++ b/src/DisplayFormatters.h @@ -4,5 +4,6 @@ class DisplayFormatters { public: - static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName); + static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName, + bool usePreset); }; diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index a0f29f10d..d5835a335 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -263,12 +263,6 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t display->drawString(x + 1, y, "USB"); } - // auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true); - - // display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode); - // if (config.display.heading_bold) - // display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode); - uint32_t currentMillis = millis(); uint32_t seconds = currentMillis / 1000; uint32_t minutes = seconds / 60; @@ -398,7 +392,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, display->drawString(nameX, getTextPositions(display)[line++], shortnameble); // === Second Row: Radio Preset === - auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); + auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset); char regionradiopreset[25]; const char *region = myRegion ? myRegion->name : NULL; if (region != nullptr) { diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 70e4127d8..4ef41ddfb 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -368,7 +368,7 @@ const char *Channels::getName(size_t chIndex) // Per mesh.proto spec, if bandwidth is specified we must ignore modemPreset enum, we assume that in that case // the app effed up and forgot to set channelSettings.name if (config.lora.use_preset) { - channelName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); + channelName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset); } else { channelName = "Custom"; } @@ -382,7 +382,8 @@ bool Channels::isDefaultChannel(ChannelIndex chIndex) const auto &ch = getByIndex(chIndex); if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { const char *name = getName(chIndex); - const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); + const char *presetName = + DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset); // Check if the name is the default derived from the modem preset if (strcmp(name, presetName) == 0) return true; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index c210d5d48..a5c293868 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -586,7 +586,8 @@ void RadioInterface::applyModemConfig() // Check if we use the default frequency slot RadioInterface::uses_default_frequency_slot = - channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels; + channel_num == + hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels; // Old frequency selection formula // float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num); From a6b8202cd40480c1fd0b9bf072f43821f00f6793 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 2 Sep 2025 21:05:14 +1000 Subject: [PATCH 10/81] Hold for 20s after GPS lock (#7801) * Hold for >20s after GPS lock GPS chips are designed to stay locked for a while to download some data and save it. This data is important for speeding up future locks, and making them higher quality. Our present configuration could make every lock perform similar to first lock. This patch sets a hold of between 20s and 10% of the lock search time after lock is acquired. This should allow the GPS to finish its work before we turn it off. Fixes https://github.com/meshtastic/firmware/issues/7466 * Remove T1000E-specific GPS holds The new code does the same thing, for all devices. * Fix publishing settings * Cleanups, removing unused variables. * ifdef log line with GPS_DEBUG * fixQual is not a bool. --- src/gps/GPS.cpp | 73 +++++++-------------- src/gps/GPS.h | 2 +- variants/nrf52840/tracker-t1000-e/variant.h | 3 +- variants/nrf52840/wio-t1000-s/variant.h | 3 +- 4 files changed, 25 insertions(+), 56 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 881021975..9ae7ae97d 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -843,9 +843,6 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) setPowerPMU(true); // Power (PMU): on writePinStandby(false); // Standby (pin): awake (not standby) setPowerUBLOX(true); // Standby (UBLOX): awake -#ifdef GNSS_AIROHA - lastFixStartMsec = 0; -#endif break; case GPS_SOFTSLEEP: @@ -863,9 +860,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) writePinStandby(true); // Standby (pin): asleep (not awake) setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed #ifdef GNSS_AIROHA - if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) { - digitalWrite(PIN_GPS_EN, LOW); - } + digitalWrite(PIN_GPS_EN, LOW); #endif break; @@ -877,9 +872,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime) writePinStandby(true); // Standby (pin): asleep setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely #ifdef GNSS_AIROHA - if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) { - digitalWrite(PIN_GPS_EN, LOW); - } + digitalWrite(PIN_GPS_EN, LOW); #endif break; } @@ -1062,6 +1055,8 @@ void GPS::down() } // If update interval long enough (or softsleep unsupported): hardsleep instead setPowerState(GPS_HARDSLEEP, sleepTime); + // Reset the fix quality to 0, since we're off. + fixQual = 0; } } @@ -1121,11 +1116,19 @@ int32_t GPS::runOnce() shouldPublish = true; } + uint8_t prev_fixQual = fixQual; bool gotLoc = lookForLocation(); if (gotLoc && !hasValidLocation) { // declare that we have location ASAP LOG_DEBUG("hasValidLocation RISING EDGE"); hasValidLocation = true; shouldPublish = true; + // Hold for 20secs after getting a lock to download ephemeris etc + fixHoldEnds = millis() + 20000; + } + + if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on. + fixHoldEnds = millis() + 20000; + shouldPublish = true; // Publish immediately, since next publish is at end of hold } bool tooLong = scheduling.searchedTooLong(); @@ -1134,8 +1137,7 @@ int32_t GPS::runOnce() // Once we get a location we no longer desperately want an update if ((gotLoc && gotTime) || tooLong) { - - if (tooLong) { + if (tooLong && !gotLoc) { // we didn't get a location during this ack window, therefore declare loss of lock if (hasValidLocation) { LOG_DEBUG("hasValidLocation FALLING EDGE"); @@ -1143,9 +1145,15 @@ int32_t GPS::runOnce() p = meshtastic_Position_init_default; hasValidLocation = false; } - - down(); - shouldPublish = true; // publish our update for this just finished acquisition window + if (millis() > fixHoldEnds) { + shouldPublish = true; // publish our update at the end of the lock hold + publishUpdate(); + down(); +#ifdef GPS_DEBUG + } else { + LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view); +#endif + } } // If state has changed do a publish @@ -1508,24 +1516,6 @@ static int32_t toDegInt(RawDegrees d) */ bool GPS::lookForTime() { - -#ifdef GNSS_AIROHA - uint8_t fix = reader.fixQuality(); - if (fix >= 1 && fix <= 5) { - if (lastFixStartMsec > 0) { - if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) { - return false; - } else { - clearBuffer(); - } - } else { - lastFixStartMsec = millis(); - return false; - } - } else { - return false; - } -#endif auto ti = reader.time; auto d = reader.date; if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed @@ -1564,25 +1554,6 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s */ bool GPS::lookForLocation() { -#ifdef GNSS_AIROHA - if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { - uint8_t fix = reader.fixQuality(); - if (fix >= 1 && fix <= 5) { - if (lastFixStartMsec > 0) { - if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) { - return false; - } else { - clearBuffer(); - } - } else { - lastFixStartMsec = millis(); - return false; - } - } else { - return false; - } - } -#endif // By default, TinyGPS++ does not parse GPGSA lines, which give us // the 2D/3D fixType (see NMEAGPS.h) // At a minimum, use the fixQuality indicator in GPGGA (FIXME?) diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 9be57017f..177cfe74b 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -159,7 +159,7 @@ class GPS : private concurrency::OSThread uint8_t fixType = 0; // fix type from GPGSA #endif - uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0; + uint32_t fixHoldEnds = 0; uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; diff --git a/variants/nrf52840/tracker-t1000-e/variant.h b/variants/nrf52840/tracker-t1000-e/variant.h index 81b4ef3fb..403552ec0 100644 --- a/variants/nrf52840/tracker-t1000-e/variant.h +++ b/variants/nrf52840/tracker-t1000-e/variant.h @@ -124,8 +124,7 @@ extern "C" { #define GPS_RTC_INT (0 + 15) // P0.15, normal is LOW, wake by HIGH #define GPS_RESETB_OUT (32 + 14) // P1.14, always input pull_up -#define GPS_FIX_HOLD_TIME 15000 // ms -#define BATTERY_PIN 2 // P0.02/AIN0, BAT_ADC +#define BATTERY_PIN 2 // P0.02/AIN0, BAT_ADC #define BATTERY_IMMUTABLE #define ADC_MULTIPLIER (2.0F) // P0.04/AIN2 is VCC_ADC, P0.05/AIN3 is CHARGER_DET, P1.03 is CHARGE_STA, P1.04 is CHARGE_DONE diff --git a/variants/nrf52840/wio-t1000-s/variant.h b/variants/nrf52840/wio-t1000-s/variant.h index eb6a34d6c..02f8a20b2 100644 --- a/variants/nrf52840/wio-t1000-s/variant.h +++ b/variants/nrf52840/wio-t1000-s/variant.h @@ -123,7 +123,6 @@ extern "C" { #define GPS_RESETB_OUT (32 + 14) // P1.14, awlays input pull_up // #define GPS_THREAD_INTERVAL 50 -#define GPS_FIX_HOLD_TIME 15000 // ms #define BATTERY_PIN 2 // #define ADC_CHANNEL ADC1_GPIO2_CHANNEL @@ -157,4 +156,4 @@ extern "C" { * Arduino objects - C++ only *----------------------------------------------------------------------------*/ -#endif // _VARIANT_WIO_SDK_WM1110_ \ No newline at end of file +#endif // _VARIANT_WIO_SDK_WM1110_ From 3b82d551760228bee12d23e4b31b672b09a58523 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 2 Sep 2025 06:17:01 -0500 Subject: [PATCH 11/81] Revert "Add gat562_mesh_tracker_pro device. (#7815)" (#7824) This reverts commit 7d1300ab66c4107f13e78769ab258760241d3331. --- boards/gat562_mesh_tracker_pro.json | 52 --- .../gat562_mesh_tracker_pro/platformio.ini | 15 - .../gat562_mesh_tracker_pro/variant.cpp | 54 ---- .../gat562_mesh_tracker_pro/variant.h | 300 ------------------ 4 files changed, 421 deletions(-) delete mode 100644 boards/gat562_mesh_tracker_pro.json delete mode 100644 variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini delete mode 100644 variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp delete mode 100644 variants/nrf52840/gat562_mesh_tracker_pro/variant.h diff --git a/boards/gat562_mesh_tracker_pro.json b/boards/gat562_mesh_tracker_pro.json deleted file mode 100644 index 92e5feb89..000000000 --- a/boards/gat562_mesh_tracker_pro.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "build": { - "arduino": { - "ldscript": "nrf52840_s140_v6.ld" - }, - "core": "nRF5", - "cpu": "cortex-m4", - "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", - "f_cpu": "64000000L", - "hwids": [ - ["0x239A", "0x8029"], - ["0x239A", "0x0029"], - ["0x239A", "0x002A"], - ["0x239A", "0x802A"] - ], - "usb_product": "GAT562 Mesh Tracker Pro", - "mcu": "nrf52840", - "variant": "gat562_mesh_tracker_pro", - "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", - "openocd_target": "nrf52840-mdk-rs" - }, - "frameworks": ["arduino", "freertos"], - "name": "GAT562 Mesh Tracker Pro", - "upload": { - "maximum_ram_size": 248832, - "maximum_size": 815104, - "speed": 115200, - "protocol": "nrfutil", - "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"], - "use_1200bps_touch": true, - "require_upload_port": true, - "wait_for_upload_port": true - }, - "url": "http://www.gat-iot.com/", - "vendor": "GAT" -} diff --git a/variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini b/variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini deleted file mode 100644 index 8052d6336..000000000 --- a/variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini +++ /dev/null @@ -1,15 +0,0 @@ -; GAT562 Mesh Tracker Pro with Trackball support -[env:gat562_mesh_tracker_pro] -extends = nrf52840_base -board = gat562_mesh_tracker_pro -board_check = true -build_flags = ${nrf52840_base.build_flags} - -I variants/nrf52840/gat562_mesh_tracker_pro - -D GAT562_MESH_TRACKER_PRO - -DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. - -DRADIOLIB_EXCLUDE_SX128X=1 - -DRADIOLIB_EXCLUDE_SX127X=1 - -DRADIOLIB_EXCLUDE_LR11X0=1 -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/gat562_mesh_tracker_pro> -lib_deps = - ${nrf52840_base.lib_deps} diff --git a/variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp b/variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp deleted file mode 100644 index 0b9a41025..000000000 --- a/variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - 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() -{ - // LED1 & LED2 - 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); - -// Initialize trackball pins as inputs with pullup -#ifdef HAS_TRACKBALL - pinMode(TB_UP, INPUT_PULLUP); - pinMode(TB_DOWN, INPUT_PULLUP); - pinMode(TB_LEFT, INPUT_PULLUP); - pinMode(TB_RIGHT, INPUT_PULLUP); - pinMode(TB_PRESS, INPUT_PULLUP); -#endif -} diff --git a/variants/nrf52840/gat562_mesh_tracker_pro/variant.h b/variants/nrf52840/gat562_mesh_tracker_pro/variant.h deleted file mode 100644 index 367e0c491..000000000 --- a/variants/nrf52840/gat562_mesh_tracker_pro/variant.h +++ /dev/null @@ -1,300 +0,0 @@ -/* - Copyright (c) 2014-2015 Arduino LLC. All right reserved. - Copyright (c) 2016 Sandeep Mistry All right reserved. - Copyright (c) 2018, Adafruit Industries (adafruit.com) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - See the GNU Lesser General Public License for more details. - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef _VARIANT_GAT562_MESH_TRACKER_PRO_ -#define _VARIANT_GAT562_MESH_TRACKER_PRO_ - -// led pin 2 (blue), see https://github.com/meshtastic/firmware/blob/master/src/mesh/NodeDB.cpp#L723 -#define RAK4630 - -/** Master clock frequency */ -#define VARIANT_MCK (64000000ul) - -#define USE_LFXO // Board uses 32khz crystal for LF -// define USE_LFRC // Board uses RC for LF - -/*---------------------------------------------------------------------------- - * Headers - *----------------------------------------------------------------------------*/ - -#include "WVariant.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -// Number of pins defined in PinDescription array -#define PINS_COUNT (48) -#define NUM_DIGITAL_PINS (48) -#define NUM_ANALOG_INPUTS (6) -#define NUM_ANALOG_OUTPUTS (0) - -// LEDs -#define PIN_LED1 (35) -#define PIN_LED2 (36) - -#define LED_BUILTIN PIN_LED1 -#define LED_CONN PIN_LED2 - -#define LED_GREEN PIN_LED1 -#define LED_BLUE PIN_LED2 - -#define LED_STATE_ON 1 // State when LED is litted - -/* - * Buttons - */ - -#define CANCEL_BUTTON_PIN 9 -#define BUTTON_NEED_PULLUP -#define CANCEL_BUTTON_ACTIVE_LOW true -#define CANCEL_BUTTON_ACTIVE_PULLUP false - -/* - * Analog pins - */ -#define PIN_A0 (5) -#define PIN_A1 (31) -#define PIN_A2 (28) -#define PIN_A3 (29) -#define PIN_A4 (30) -#define PIN_A5 (31) -#define PIN_A6 (0xff) -#define PIN_A7 (0xff) - -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -static const uint8_t A6 = PIN_A6; -static const uint8_t A7 = PIN_A7; -#define ADC_RESOLUTION 14 - -// Other pins -#define PIN_AREF (2) -#define PIN_NFC1 (9) -#define PIN_NFC2 (10) - -static const uint8_t AREF = PIN_AREF; - -/* - * Serial interfaces - */ -#define PIN_SERIAL1_RX (15) -#define PIN_SERIAL1_TX (16) - -// Connected to Jlink CDC -#define PIN_SERIAL2_RX (8) -#define PIN_SERIAL2_TX (6) - -/* - * SPI Interfaces - */ -#define SPI_INTERFACES_COUNT 2 - -#define PIN_SPI_MISO (45) -#define PIN_SPI_MOSI (44) -#define PIN_SPI_SCK (43) - -#define PIN_SPI1_MISO (29) // (0 + 29) -#define PIN_SPI1_MOSI (30) // (0 + 30) -#define PIN_SPI1_SCK (3) // (0 + 3) - -static const uint8_t SS = 42; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; - -/* - * eink display pins - */ - -// #define PIN_EINK_CS (0 + 26) -// #define PIN_EINK_BUSY (0 + 4) -// #define PIN_EINK_DC (0 + 17) -// #define PIN_EINK_RES (-1) -// #define PIN_EINK_SCLK (0 + 3) -// #define PIN_EINK_MOSI (0 + 30) // also called SDI - -// #define USE_EINK - -// Display - OLED connected via I2C -#define HAS_SCREEN 1 -#define USE_SSD1306 - -// RAKRGB -// #define HAS_NCP5623 - -/* - * Wire Interfaces - */ -#define WIRE_INTERFACES_COUNT 1 - -#define PIN_WIRE_SDA (13) -#define PIN_WIRE_SCL (14) - -// QSPI Pins -#define PIN_QSPI_SCK 3 -#define PIN_QSPI_CS 26 -#define PIN_QSPI_IO0 30 -#define PIN_QSPI_IO1 29 -#define PIN_QSPI_IO2 28 -#define PIN_QSPI_IO3 2 - -// On-board QSPI Flash -#define EXTERNAL_FLASH_DEVICES IS25LP080D -#define EXTERNAL_FLASH_USE_QSPI - -/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports - RAK5005-O <-> nRF52840 - IO1 <-> P0.17 (Arduino GPIO number 17) - IO2 <-> P1.02 (Arduino GPIO number 34) - IO3 <-> P0.21 (Arduino GPIO number 21) - IO4 <-> P0.04 (Arduino GPIO number 4) - IO5 <-> P0.09 (Arduino GPIO number 9) - IO6 <-> P0.10 (Arduino GPIO number 10) - IO7 <-> P0.28 (Arduino GPIO number 28) - SW1 <-> P0.01 (Arduino GPIO number 1) - A0 <-> P0.04/AIN2 (Arduino Analog A2 - A1 <-> P0.31/AIN7 (Arduino Analog A7 - SPI_CS <-> P0.26 (Arduino GPIO number 26) - */ - -// RAK4630 LoRa module - -/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ ) - -P1.10 NSS SPI NSS (Arduino GPIO number 42) -P1.11 SCK SPI CLK (Arduino GPIO number 43) -P1.12 MOSI SPI MOSI (Arduino GPIO number 44) -P1.13 MISO SPI MISO (Arduino GPIO number 45) -P1.14 BUSY BUSY signal (Arduino GPIO number 46) -P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47) -P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38) - -Important for successful SX1262 initialization: - -* Setup DIO2 to control the antenna switch -* Setup DIO3 to control the TCXO power supply -* Setup the SX1262 to use it's DCDC regulator and not the LDO -* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the -control of the antenna switch - -SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG - -*/ - -// configure the SET pin on the RAK12039 sensor board to disable the sensor while not reading -// air quality telemetry. PIN_NFC2 doesn't seem to be used anywhere else in the codebase, but if -// you're having problems with your node behaving weirdly when a RAK12039 board isn't connected, -// try disabling this. -// #define PMSA003I_ENABLE_PIN PIN_NFC2 - -// #define DETECTION_SENSOR_EN 4 - -#define USE_SX1262 -#define SX126X_CS (42) -#define SX126X_DIO1 (47) -#define SX126X_BUSY (46) -#define SX126X_RESET (38) -// #define SX126X_TXEN (39) -// #define SX126X_RXEN (37) -#define SX126X_POWER_EN (37) -// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -// Testing USB detection -#define NRF_APM - -// enables 3.3V periphery like GPS or IO Module -// Do not toggle this for GPS power savings -#define PIN_3V3_EN (34) - -// RAK1910 GPS module -// If using the wisblock GPS module and pluged into Port A on WisBlock base -// IO1 is hooked to PPS (pin 12 on header) = gpio 17 -// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). -// Therefore must be 1 to keep peripherals powered -// Power is on the controllable 3V3_S rail -// #define PIN_GPS_RESET (34) -// #define PIN_GPS_EN PIN_3V3_EN -#define PIN_GPS_PPS (17) // Pulse per second input from the GPS - -#define GPS_BAUDRATE 9600 - -#define GPS_RX_PIN PIN_SERIAL1_RX -#define GPS_TX_PIN PIN_SERIAL1_TX - -// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press - -// RAK12002 RTC Module -// #define RV3028_RTC (uint8_t)0b1010010 - -// RAK18001 Buzzer in Slot C -// #define PIN_BUZZER 21 // IO3 is PWM2 -// NEW: set this via protobuf instead! - -// Battery -// The battery sense is hooked to pin A0 (5) -#define BATTERY_PIN PIN_A0 -// and has 12 bit resolution -#define BATTERY_SENSE_RESOLUTION_BITS 12 -#define BATTERY_SENSE_RESOLUTION 4096.0 -#undef AREF_VOLTAGE -#define AREF_VOLTAGE 3.0 -#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 - -// #define PIN_ETHERNET_RESET 21 -// #define PIN_ETHERNET_SS PIN_EINK_CS -// #define ETH_SPI_PORT SPI1 -// #define AQ_SET_PIN 10 - -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -// Trackball Configuration -// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -#define CANNED_MESSAGE_MODULE_ENABLE 1 -#define CANNED_MESSAGE_ADD_CONFIRMATION 1 - -// Trackball pins -#define HAS_TRACKBALL 1 -#define TB_LEFT 30 // P0.30 -#define TB_DOWN 4 // P0.04 -#define TB_RIGHT 31 // P0.31 -#define TB_UP 28 // P0.28 -#define TB_PRESS 26 // P0.26 (SELECT) -#define TB_DIRECTION FALLING - -#ifdef __cplusplus -} -#endif - -/*---------------------------------------------------------------------------- - * Arduino objects - C++ only - *----------------------------------------------------------------------------*/ - -#endif From 3040e5a7bb4c89ba5f22ecc95ec751c0cfd3e301 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 2 Sep 2025 21:40:59 +1000 Subject: [PATCH 12/81] Fix GPS that hard code 2080 as the start time. (#7803) * Fix GPS that hard code 2080 as the start time. Some GPS chips, such as the AG3335 in T1000e and L96 have a hardcoded time of 2080-01-05 when they start up. To fix that in a way that seems permanent, let's ignore times that are more than 40 years since the firmware was built. We should followup in late 2039 to see if any changes are needed. Reported-By: @b8b8 * Update src/gps/RTC.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Put FORTY_YEARS in header and use in both places. * Restore Ben's nicer log lines. --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/gps/RTC.cpp | 8 ++++++++ src/gps/RTC.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index ceb79eebf..e208e2df9 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -132,6 +132,10 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd if (tv->tv_sec < BUILD_EPOCH) { LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); return RTCSetResultInvalidTime; + } else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) { + LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH, + BUILD_EPOCH + FORTY_YEARS); + return RTCSetResultInvalidTime; } #endif @@ -250,6 +254,10 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t) if (tv.tv_sec < BUILD_EPOCH) { LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); return RTCSetResultInvalidTime; + } else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) { + LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH, + BUILD_EPOCH + FORTY_YEARS); + return RTCSetResultInvalidTime; } #endif diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 010be6886..03350823c 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -55,3 +55,6 @@ time_t gm_mktime(struct tm *tm); #define SEC_PER_DAY 86400 #define SEC_PER_HOUR 3600 #define SEC_PER_MIN 60 +#ifdef BUILD_EPOCH +#define FORTY_YEARS (40UL * 365 * SEC_PER_DAY) // probably time to update your firmware +#endif From 9b1fb795d7f6f83b0de959d824a82e38458b346f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 06:41:48 -0500 Subject: [PATCH 13/81] Upgrade trunk (#7822) 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 651e25b2a..3b152f452 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,7 +9,7 @@ plugins: lint: enabled: - checkov@3.2.467 - - renovate@41.90.1 + - renovate@41.91.3 - prettier@3.6.2 - trufflehog@3.90.5 - yamllint@1.37.1 From 0bd4cefad3e5fe419901343388c466ae82e10308 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Tue, 2 Sep 2025 13:08:57 +0100 Subject: [PATCH 14/81] Make ExternalNotification show up in excluded_modules, more STM32 modules (#7797) * Show ExternalNotification as excluded if it is * Enable ExternalNotification, SerialModule and RangeTest on STM32WL * Misc fixes for #7797 - ARCH_STM32 -> ARCH_STM32WL, use less flash by dropping weather station support for serialmodule, set tx/rx pins before begin * Enable Serial1 on RAK3172, make SerialModule use it (console is on LPUART1) * Fix SerialModule on RAK3172, fix board definition of RAK3172 to include the right pin mapping. --- boards/wiscore_rak3172.json | 2 +- src/main.cpp | 2 +- src/modules/ExternalNotificationModule.cpp | 3 ++- src/modules/Modules.cpp | 9 +++---- src/modules/RangeTestModule.cpp | 4 +-- src/modules/SerialModule.cpp | 30 +++++++++++++++++----- src/modules/SerialModule.h | 4 +-- variants/stm32/rak3172/platformio.ini | 4 +++ 8 files changed, 38 insertions(+), 20 deletions(-) diff --git a/boards/wiscore_rak3172.json b/boards/wiscore_rak3172.json index 714e09115..69ee506b4 100644 --- a/boards/wiscore_rak3172.json +++ b/boards/wiscore_rak3172.json @@ -5,7 +5,7 @@ }, "core": "stm32", "cpu": "cortex-m4", - "extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_GENERIC_WLE5CCUX", + "extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_RAK3172_MODULE", "f_cpu": "48000000L", "mcu": "stm32wle5ccu", "variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U", diff --git a/src/main.cpp b/src/main.cpp index 111709d07..8263a3144 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1525,7 +1525,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata() #if ((!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS) deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG; #endif -#if NO_EXT_GPIO +#if NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_EXTNOTIF_CONFIG; #endif // Only edge case here is if we apply this a device with built in Accelerometer and want to detect interrupts diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 1f871f87e..2f2934984 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -364,9 +364,10 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.alert_message_buzzer = true; if (moduleConfig.external_notification.enabled) { +#if !defined(MESHTASTIC_EXCLUDE_INPUTBROKER) if (inputBroker) // put our callback in the inputObserver list inputObserver.observe(inputBroker); - +#endif if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) { memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 0d405fa81..b9b4dd3e5 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -88,7 +88,7 @@ #include "modules/StoreForwardModule.h" #endif #endif -#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO) + #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION #include "modules/ExternalNotificationModule.h" #endif @@ -98,7 +98,6 @@ #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_SERIAL #include "modules/SerialModule.h" #endif -#endif #if !MESHTASTIC_EXCLUDE_DROPZONE #include "modules/DropzoneModule.h" @@ -246,8 +245,8 @@ void setupModules() #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR new PowerTelemetryModule(); #endif -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ - !defined(CONFIG_IDF_TARGET_ESP32C3) +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \ + !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) #if !MESHTASTIC_EXCLUDE_SERIAL if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { new SerialModule(); @@ -268,13 +267,11 @@ void setupModules() storeForwardModule = new StoreForwardModule(); #endif #endif -#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO) #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION externalNotificationModule = new ExternalNotificationModule(); #endif #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS new RangeTestModule(); -#endif #endif } else { #if !MESHTASTIC_EXCLUDE_ADMIN diff --git a/src/modules/RangeTestModule.cpp b/src/modules/RangeTestModule.cpp index 6f3d69acf..d1d2d9ead 100644 --- a/src/modules/RangeTestModule.cpp +++ b/src/modules/RangeTestModule.cpp @@ -31,7 +31,7 @@ uint32_t packetSequence = 0; int32_t RangeTestModule::runOnce() { -#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) +#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_STM32WL) || defined(ARCH_PORTDUINO) /* Uncomment the preferences below if you want to use the module @@ -130,7 +130,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies) ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket &mp) { -#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) +#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_STM32WL) || defined(ARCH_PORTDUINO) if (moduleConfig.range_test.enabled) { diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 880768839..7485f1c2d 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -49,8 +49,8 @@ #include "meshSolarApp.h" #endif -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ - !defined(CONFIG_IDF_TARGET_ESP32C3) +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \ + !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) #define RX_BUFFER 256 #define TIMEOUT 250 @@ -67,7 +67,7 @@ SerialModuleRadio *serialModuleRadio; defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial; -#elif defined(CONFIG_IDF_TARGET_ESP32C6) +#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial1; #else @@ -173,7 +173,18 @@ int32_t SerialModule::runOnce() Serial.begin(baud); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } - +#elif defined(ARCH_STM32WL) +#ifndef RAK3172 + HardwareSerial *serialInstance = &Serial2; +#else + HardwareSerial *serialInstance = &Serial1; +#endif + if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { + serialInstance->setTx(moduleConfig.serial.txd); + serialInstance->setRx(moduleConfig.serial.rxd); + } + serialInstance->begin(baud); + serialInstance->setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); #elif defined(ARCH_ESP32) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { @@ -260,8 +271,13 @@ int32_t SerialModule::runOnce() while (Serial1.available()) { serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); #else - while (Serial2.available()) { - serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); +#ifndef RAK3172 + HardwareSerial *serialInstance = &Serial2; +#else + HardwareSerial *serialInstance = &Serial1; +#endif + while (serialInstance->available()) { + serialPayloadSize = serialInstance->readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); #endif serialModuleRadio->sendPayload(); } @@ -511,7 +527,7 @@ ParsedLine parseLine(const char *line) void SerialModule::processWXSerial() { #if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \ - !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) + !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static double dir_sum_sin = 0; diff --git a/src/modules/SerialModule.h b/src/modules/SerialModule.h index 1c74c927c..dbe4f75db 100644 --- a/src/modules/SerialModule.h +++ b/src/modules/SerialModule.h @@ -8,8 +8,8 @@ #include #include -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ - !defined(CONFIG_IDF_TARGET_ESP32C3) +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \ + !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) class SerialModule : public StreamAPI, private concurrency::OSThread { diff --git a/variants/stm32/rak3172/platformio.ini b/variants/stm32/rak3172/platformio.ini index 4f9edbb92..a12b9f21c 100644 --- a/variants/stm32/rak3172/platformio.ini +++ b/variants/stm32/rak3172/platformio.ini @@ -6,6 +6,10 @@ board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem build_flags = ${stm32_base.build_flags} -Ivariants/stm32/rak3172 + -DRAK3172 + -DENABLE_HWSERIAL1 + -DPIN_SERIAL1_RX=PB7 + -DPIN_SERIAL1_TX=PB6 -DPIN_WIRE_SDA=PA11 -DPIN_WIRE_SCL=PA12 -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 From 655c6b51fec6164d58db7a9746820536d848d825 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 2 Sep 2025 09:50:15 -0500 Subject: [PATCH 15/81] Try-fix Cardkb detection (#7825) * Try-fix: CardKB detection regression * Correct macro --- src/input/cardKbI2cImpl.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 9b0926a1d..cb03eb4ff 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -13,7 +13,11 @@ void CardKbI2cImpl::init() if (cardkb_found.address == 0x00) { LOG_DEBUG("Rescan for I2C keyboard"); uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR}; +#if defined(T_LORA_PAGER) uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]); +#else + uint8_t i2caddr_asize = 5; +#endif auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); #if WIRE_INTERFACES_COUNT == 2 From edb7ec58c6407a062f91469fb2365541f4d34b4c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 11:58:57 -0500 Subject: [PATCH 16/81] chore(deps): update platform-native digest to c490bcd (#7814) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- arch/portduino/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 20b3f8e3d..a6c1dff66 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -2,7 +2,7 @@ [portduino_base] platform = # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop - https://github.com/meshtastic/platform-native/archive/37d986499ce24511952d7146db72d667c6bdaff7.zip + https://github.com/meshtastic/platform-native/archive/c490bcd019e0658404088a61b96e653c9da22c45.zip framework = arduino build_src_filter = From c66125114f1764d38d66397974b7c1531c258ac7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:17:00 -0500 Subject: [PATCH 17/81] chore(deps): update meshtastic/device-ui digest to 8019704 (#7830) 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 ef0fef791..67c3f8a8c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -118,7 +118,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/a3e0e1be372d069f47b4c19d718f5267251744d7.zip + https://github.com/meshtastic/device-ui/archive/8019704395b7539600d581330499208edcd80804.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From b59409bec0f5c37ff59bd60160e45474dc58edcd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 18:01:31 -0500 Subject: [PATCH 18/81] chore(deps): update caveman99-stm32-crypto digest to 1aa30eb (#7808) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- arch/stm32/stm32.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini index d91607a7d..8b7d256b3 100644 --- a/arch/stm32/stm32.ini +++ b/arch/stm32/stm32.ini @@ -50,7 +50,7 @@ lib_deps = ${radiolib_base.lib_deps} # renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main - https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip + https://github.com/caveman99/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip lib_ignore = OneButton From 8a8f60d129701cbe0063541b81df263fb86fea93 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:08:05 -0500 Subject: [PATCH 19/81] Update protobufs (#7831) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/localonly.pb.h | 2 +- src/mesh/generated/meshtastic/module_config.pb.h | 13 +++++++++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/protobufs b/protobufs index 4c4427c4a..34f0c8115 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 4c4427c4a73c86fed7dc8632188bb8be95349d81 +Subproject commit 34f0c8115d95f9f4be6d600095428a03833ac98e diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index f47091384..9b6330596 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -360,7 +360,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 2271 +#define meshtastic_BackupPreferences_size 2273 #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 ca8dcd5fb..da224fb94 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -188,7 +188,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_LocalConfig_size #define meshtastic_LocalConfig_size 747 -#define meshtastic_LocalModuleConfig_size 669 +#define meshtastic_LocalModuleConfig_size 671 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h index b27f5f515..16c4c230c 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.h +++ b/src/mesh/generated/meshtastic/module_config.pb.h @@ -317,6 +317,9 @@ typedef struct _meshtastic_ModuleConfig_RangeTestConfig { /* Bool value indicating that this node should save a RangeTest.csv file. ESP32 Only */ bool save; + /* Bool indicating that the node should cleanup / destroy it's RangeTest.csv file. + ESP32 Only */ + bool clear_on_reboot; } meshtastic_ModuleConfig_RangeTestConfig; /* Configuration for both device and environment metrics */ @@ -519,7 +522,7 @@ extern "C" { #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} -#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0} +#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #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} @@ -535,7 +538,7 @@ extern "C" { #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} -#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} +#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #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} @@ -610,6 +613,7 @@ extern "C" { #define meshtastic_ModuleConfig_RangeTestConfig_enabled_tag 1 #define meshtastic_ModuleConfig_RangeTestConfig_sender_tag 2 #define meshtastic_ModuleConfig_RangeTestConfig_save_tag 3 +#define meshtastic_ModuleConfig_RangeTestConfig_clear_on_reboot_tag 4 #define meshtastic_ModuleConfig_TelemetryConfig_device_update_interval_tag 1 #define meshtastic_ModuleConfig_TelemetryConfig_environment_update_interval_tag 2 #define meshtastic_ModuleConfig_TelemetryConfig_environment_measurement_enabled_tag 3 @@ -803,7 +807,8 @@ X(a, STATIC, SINGULAR, BOOL, is_server, 6) #define meshtastic_ModuleConfig_RangeTestConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, UINT32, sender, 2) \ -X(a, STATIC, SINGULAR, BOOL, save, 3) +X(a, STATIC, SINGULAR, BOOL, save, 3) \ +X(a, STATIC, SINGULAR, BOOL, clear_on_reboot, 4) #define meshtastic_ModuleConfig_RangeTestConfig_CALLBACK NULL #define meshtastic_ModuleConfig_RangeTestConfig_DEFAULT NULL @@ -901,7 +906,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg; #define meshtastic_ModuleConfig_MapReportSettings_size 14 #define meshtastic_ModuleConfig_NeighborInfoConfig_size 10 #define meshtastic_ModuleConfig_PaxcounterConfig_size 30 -#define meshtastic_ModuleConfig_RangeTestConfig_size 10 +#define meshtastic_ModuleConfig_RangeTestConfig_size 12 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24 From 6c89ea7cee2f7422bddf96d8367112b505c51df5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 3 Sep 2025 06:16:00 -0500 Subject: [PATCH 20/81] chore(deps): update platform-native digest to c490bcd (#7814) (#7832) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- arch/portduino/portduino.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index 20b3f8e3d..a6c1dff66 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -2,7 +2,7 @@ [portduino_base] platform = # renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop - https://github.com/meshtastic/platform-native/archive/37d986499ce24511952d7146db72d667c6bdaff7.zip + https://github.com/meshtastic/platform-native/archive/c490bcd019e0658404088a61b96e653c9da22c45.zip framework = arduino build_src_filter = From 5850a7cd6b503bc57216446cb8ec2260c3c39e9b Mon Sep 17 00:00:00 2001 From: "Daniel.Cao" <144674500+DanielCao0@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:20:19 +0800 Subject: [PATCH 21/81] Add RAK WisMesh Tap V2 (ESP32S3) Hardware Variant (#7741) * Add initial variant and platformio configuration for RAK WISMESHTAP V2 * Add initial variant and platformio configuration for rak wismesh tap v2 * Remove unnecessary Meshtastic build flags from rak_wismesh_tap_v2 configuration * Enable LGFX button support in rak_wismesh_tap_v2 configuration * Revert "Enable LGFX button support in rak_wismesh_tap_v2 configuration" This reverts commit 2bd2c1a03b1b8a224c440049b7aff8a15bb54dbf. --------- Co-authored-by: Daniel.Cao --- .../esp32s3/rak_wismesh_tap_v2/pins_arduino.h | 28 ++++++ .../esp32s3/rak_wismesh_tap_v2/platformio.ini | 87 +++++++++++++++++++ variants/esp32s3/rak_wismesh_tap_v2/variant.h | 71 +++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h create mode 100644 variants/esp32s3/rak_wismesh_tap_v2/platformio.ini create mode 100644 variants/esp32s3/rak_wismesh_tap_v2/variant.h diff --git a/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h new file mode 100644 index 000000000..15a26e991 --- /dev/null +++ b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h @@ -0,0 +1,28 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "variant.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 = 9; +static const uint8_t SCL = 40; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 12; +static const uint8_t MOSI = 11; +static const uint8_t MISO = 10; +static const uint8_t SCK = 13; + +#define SPI_MOSI (11) +#define SPI_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/rak_wismesh_tap_v2/platformio.ini b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini new file mode 100644 index 000000000..8b86e0217 --- /dev/null +++ b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini @@ -0,0 +1,87 @@ +; rak_wismeshtap2 rak3112 + +[rak_wismeshtap_s3] +extends = esp32s3_base +board = wiscore_rak3312 +board_check = true +upload_protocol = esptool +board_build.partitions = default_8MB.csv + +build_flags = + ${esp32_base.build_flags} + -D RAK3312 + -D RAK_WISMESH_TAP_V2 + -I variants/esp32s3/rak_wismesh_tap_v2 + +lib_deps = + ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@^1.2.0 + +[ft5x06] +extends = mesh_tab_base +build_flags = + -D LGFX_TOUCH=FT5x06 + -D LGFX_TOUCH_I2C_FREQ=100000 + -D LGFX_TOUCH_I2C_PORT=0 + -D LGFX_TOUCH_I2C_ADDR=0x38 + -D LGFX_TOUCH_I2C_SDA=9 + -D LGFX_TOUCH_I2C_SCL=40 + -D LGFX_TOUCH_RST=-1 + -D LGFX_TOUCH_INT=39 + +[env:rak_wismesh_tap_v2-tft] +extends = rak_wismeshtap_s3 + +build_flags = + ${rak_wismeshtap_s3.build_flags} + -D CONFIG_ARDUHAL_ESP_LOG + -D CONFIG_ARDUHAL_LOG_COLORS=1 + -D CONFIG_DISABLE_HAL_LOCKS=1 + -D LV_LVGL_H_INCLUDE_SIMPLE + -D LV_CONF_INCLUDE_SIMPLE + -D LV_COMP_CONF_INCLUDE_SIMPLE + -D LV_USE_SYSMON=0 + -D LV_USE_PROFILER=0 + -D LV_USE_PERF_MONITOR=0 + -D LV_USE_MEM_MONITOR=0 + -D LV_USE_LOG=0 + -D LV_BUILD_TEST=0 + -D USE_LOG_DEBUG + -D LOG_DEBUG_INC=\"DebugConfiguration.h\" + -D RADIOLIB_SPI_PARANOID=0 + -D INPUTDRIVER_BUTTON_TYPE=0 + -D HAS_SDCARD + -D HAS_SCREEN=0 + -D HAS_TFT=1 + -D USE_PIN_BUZZER=PIN_BUZZER + -D RAM_SIZE=5120 + -D LGFX_DRIVER_TEMPLATE + -D LGFX_DRIVER=LGFX_GENERIC + -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_GENERIC.h\" + -D LGFX_PIN_SCK=13 + -D LGFX_PIN_MOSI=11 + -D LGFX_PIN_MISO=10 + -D LGFX_PIN_DC=42 + -D LGFX_PIN_CS=12 + -D LGFX_PIN_RST=-1 + -D LGFX_PIN_BL=41 + -D VIEW_320x240 + -D USE_PACKET_API + ${ft5x06.build_flags} + -D LGFX_SCREEN_WIDTH=240 + -D LGFX_SCREEN_HEIGHT=320 + -D LGFX_PANEL=ST7789 + -D LGFX_ROTATION=1 + -D LGFX_TOUCH_X_MIN=0 + -D LGFX_TOUCH_X_MAX=239 + -D LGFX_TOUCH_Y_MIN=0 + -D LGFX_TOUCH_Y_MAX=319 + -D LGFX_TOUCH_ROTATION=2 + -D LGFX_CFG_HOST=SPI3_HOST + -D MAP_FULL_REDRAW=1 + +lib_deps = + ${rak_wismeshtap_s3.lib_deps} + ${device-ui_base.lib_deps} + + diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h new file mode 100644 index 000000000..8468c557e --- /dev/null +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -0,0 +1,71 @@ +#ifndef _VARIANT_RAK_WISMESHTAP_V2_H +#define _VARIANT_RAK_WISMESHTAP_V2_H + +#define I2C_SDA 9 +#define I2C_SCL 40 + +#define USE_SX1262 + +#define LORA_SCK 5 +#define LORA_MISO 3 +#define LORA_MOSI 6 +#define LORA_CS 7 +#define LORA_RESET 8 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 47 +#define SX126X_BUSY 48 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif + +#define SX126X_POWER_EN (4) + +#define PIN_POWER_EN PIN_3V3_EN +#define PIN_3V3_EN (14) + +#define LED_GREEN 46 +#define LED_BLUE 45 + +#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) + +#define LED_STATE_ON 1 // State when LED is litted + +#define HAS_GPS 1 +#define GPS_TX_PIN 43 +#define GPS_RX_PIN 44 + +#define SPI_MOSI (11) +#define SPI_SCK (13) +#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 +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_MULTIPLIER 1.667 + +#define PIN_BUZZER 38 + +#define HAS_SDCARD 1 +#define SDCARD_USE_SPI1 1 +#define SDCARD_CS 2 + +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 + +#define SD_SPI_FREQUENCY 50000000 + +#endif \ No newline at end of file From 8aae4f1b9df3d834ffbada32183594fe920c187b Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:22:57 +0200 Subject: [PATCH 22/81] Update device-install scripts for T-LoRa Pager (#7833) * add tlora-pager to device install scripts + fixes * replace deprecated commands (write_flash) --- bin/device-install.bat | 16 ++++++++-------- bin/device-install.sh | 14 +++++++------- bin/device-update.bat | 6 +++--- bin/device-update.sh | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/bin/device-install.bat b/bin/device-install.bat index 93b2fcec1..24c841e4b 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -14,11 +14,11 @@ SET "LOGCOUNTER=0" SET "BPS_RESET=0" @REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable. -SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone" +SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv" SET "C3=esp32c3" @REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable. SET "BIGDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger" -SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite t-watch-s3" +SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv" GOTO getopts :help @@ -233,14 +233,14 @@ IF NOT EXIST !SPIFFS_FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!S @REM Flashing operations. CALL :LOG_MESSAGE INFO "Trying to flash "!FILENAME!", but first erasing and writing system information..." -CALL :RUN_ESPTOOL !ESPTOOL_BAUD! erase_flash || GOTO eof -CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x00 "!FILENAME!" || GOTO eof +CALL :RUN_ESPTOOL !ESPTOOL_BAUD! erase-flash || GOTO eof +CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash 0x00 "!FILENAME!" || GOTO eof CALL :LOG_MESSAGE INFO "Trying to flash BLEOTA "!OTA_FILENAME!" at OTA_OFFSET !OTA_OFFSET!..." -CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !OTA_OFFSET! "!OTA_FILENAME!" || GOTO eof +CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !OTA_OFFSET! "!OTA_FILENAME!" || GOTO eof CALL :LOG_MESSAGE INFO "Trying to flash SPIFFS "!SPIFFS_FILENAME!" at SPIFFS_OFFSET !SPIFFS_OFFSET!..." -CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !SPIFFS_OFFSET! "!SPIFFS_FILENAME!" || GOTO eof +CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !SPIFFS_OFFSET! "!SPIFFS_FILENAME!" || GOTO eof CALL :LOG_MESSAGE INFO "Script complete!." @@ -252,9 +252,9 @@ EXIT /B %ERRORLEVEL% :RUN_ESPTOOL @REM Subroutine used to run ESPTOOL_CMD with arguments. @REM Also handles %ERRORLEVEL%. -@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename] +@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write-flash] [OFFSET] [Filename] @REM. -@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin" +@REM Example:: CALL :RUN_ESPTOOL 115200 write-flash 0x10000 "firmwarefile.bin" IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4" CALL :RESET_ERROR !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4 diff --git a/bin/device-install.sh b/bin/device-install.sh index 4674113b6..c2ba7539a 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -33,10 +33,9 @@ BIGDB_16MB=( "m5stack-cores3" "station-g2" "t-eth-elite" + "tlora-pager" "t-watch-s3" - "elecrow-adv-35-tft" - "elecrow-adv-24-28-tft" - "elecrow-adv1-43-50-70-tft" + "elecrow-adv" ) S3_VARIANTS=( "s3" @@ -47,6 +46,7 @@ S3_VARIANTS=( "station-g2" "unphone" "t-eth-elite" + "tlora-pager" "mesh-tab" "dreamcatcher" "ESP32-S3-Pico" @@ -201,12 +201,12 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then fi echo "Trying to flash ${FILENAME}, but first erasing and writing system information" - $ESPTOOL_CMD erase_flash - $ESPTOOL_CMD write_flash 0x00 "${FILENAME}" + $ESPTOOL_CMD erase-flash + $ESPTOOL_CMD write-flash 0x00 "${FILENAME}" echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}" - $ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}" + $ESPTOOL_CMD write-flash $OTA_OFFSET "${OTAFILE}" echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}" - $ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}" + $ESPTOOL_CMD write-flash $OFFSET "${SPIFFSFILE}" else show_help diff --git a/bin/device-update.bat b/bin/device-update.bat index 6d55294a7..9077ae5b9 100755 --- a/bin/device-update.bat +++ b/bin/device-update.bat @@ -133,7 +133,7 @@ IF %CHANGE_MODE% EQU 1 ( @REM Flashing operations. CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..." -CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x10000 "!FILENAME!" || GOTO eof +CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash 0x10000 "!FILENAME!" || GOTO eof CALL :LOG_MESSAGE INFO "Script complete!." @@ -145,9 +145,9 @@ EXIT /B %ERRORLEVEL% :RUN_ESPTOOL @REM Subroutine used to run ESPTOOL_CMD with arguments. @REM Also handles %ERRORLEVEL%. -@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename] +@REM CALL :RUN_ESPTOOL [Baud] [erase-flash|write-flash] [OFFSET] [Filename] @REM. -@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin" +@REM Example:: CALL :RUN_ESPTOOL 115200 write-flash 0x10000 "firmwarefile.bin" IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4" CALL :RESET_ERROR !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4 diff --git a/bin/device-update.sh b/bin/device-update.sh index 2196d3af9..7f603e070 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -75,7 +75,7 @@ fi if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then echo "Trying to flash update ${FILENAME}" - $ESPTOOL_CMD --baud 115200 write_flash 0x10000 "${FILENAME}" + $ESPTOOL_CMD --baud 115200 write-flash 0x10000 "${FILENAME}" else show_help echo "Invalid file: ${FILENAME}" From e8367894f24a048d141f9a8d0c4a367319b765bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 06:45:14 -0500 Subject: [PATCH 23/81] Upgrade trunk (#7835) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> Co-authored-by: Ben Meadors --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 3b152f452..874715638 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.467 - - renovate@41.91.3 + - checkov@3.2.469 + - renovate@41.93.2 - prettier@3.6.2 - trufflehog@3.90.5 - yamllint@1.37.1 From a0c0388dd9cf6c1abed0da7f1b92f40c9dfda4dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:00:08 -0500 Subject: [PATCH 24/81] chore(deps): update meshtastic/device-ui digest to 10f0244 (#7840) 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 67c3f8a8c..b0f73bc09 100644 --- a/platformio.ini +++ b/platformio.ini @@ -118,7 +118,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/8019704395b7539600d581330499208edcd80804.zip + https://github.com/meshtastic/device-ui/archive/10f02441ec7dcd099c4c5165c709afc3e0e3cb88.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 1c1c0cc79124bcca8dbbeadf70ee41fbc0e678c5 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Wed, 3 Sep 2025 17:50:26 -0500 Subject: [PATCH 25/81] Portduino config refactor (#7796) * Start portduino_config refactor * refactor GPIOs to new portduino_config * More portduino_config work * More conversion to portduino_config * Finish portduino_config transition * trunk * yaml output work * Simplify the GPIO config * Trunk --- .clusterfuzzlite/router_fuzzer.cpp | 4 +- src/DebugConfiguration.cpp | 2 +- src/RedirectablePrint.cpp | 14 +- src/gps/GPS.cpp | 2 +- src/graphics/Screen.cpp | 4 +- src/graphics/TFTDisplay.cpp | 88 +-- src/graphics/tftSetup.cpp | 96 ++-- src/input/LinuxInput.cpp | 4 +- src/input/TouchScreenImpl1.cpp | 2 +- src/input/TrackballInterruptBase.h | 2 +- src/main.cpp | 90 ++- src/mesh/LR11x0Interface.cpp | 6 +- src/mesh/NodeDB.cpp | 6 +- src/mesh/RF95Interface.cpp | 22 +- src/mesh/RadioLibInterface.cpp | 2 +- src/mesh/Router.cpp | 4 +- src/mesh/SX126xInterface.cpp | 18 +- src/mesh/SX128xInterface.cpp | 43 +- src/mesh/raspihttp/PiWebServer.cpp | 10 +- src/modules/Telemetry/HostMetrics.cpp | 10 +- src/platform/portduino/PortduinoGlue.cpp | 534 +++++++----------- src/platform/portduino/PortduinoGlue.h | 518 ++++++++++++++--- src/platform/portduino/architecture.h | 10 +- variants/native/portduino-buildroot/variant.h | 4 +- variants/native/portduino/variant.h | 6 +- 25 files changed, 855 insertions(+), 646 deletions(-) diff --git a/.clusterfuzzlite/router_fuzzer.cpp b/.clusterfuzzlite/router_fuzzer.cpp index bc4d248db..71e88dbff 100644 --- a/.clusterfuzzlite/router_fuzzer.cpp +++ b/.clusterfuzzlite/router_fuzzer.cpp @@ -76,7 +76,7 @@ bool loopCanSleep() // Called just prior to starting Meshtastic. Allows for setting config values before startup. void lateInitVariant() { - settingsMap[logoutputlevel] = level_error; + portduino_config.logoutputlevel = level_error; channelFile.channels[0] = meshtastic_Channel{ .has_settings = true, .settings = @@ -132,7 +132,7 @@ int portduino_main(int argc, char **argv); // Renamed "main" function from Mesht // Start Meshtastic in a thread and wait till it has reached the ON state. int LLVMFuzzerInitialize(int *argc, char ***argv) { - settingsMap[maxtophone] = 5; + portduino_config.maxtophone = 5; meshtasticThread = std::thread([program = *argv[0]]() { char nodeIdStr[12]; diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp index 1c081ae29..d65c4f1e8 100644 --- a/src/DebugConfiguration.cpp +++ b/src/DebugConfiguration.cpp @@ -146,7 +146,7 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess { int result; #ifdef ARCH_PORTDUINO - bool utf = !settingsMap[ascii_logs]; + bool utf = !portduino_config.ascii_logs; #else bool utf = true; #endif diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp index 7c8d77651..efab84399 100644 --- a/src/RedirectablePrint.cpp +++ b/src/RedirectablePrint.cpp @@ -57,7 +57,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l #endif #ifdef ARCH_PORTDUINO - bool color = !settingsMap[ascii_logs]; + bool color = !portduino_config.ascii_logs; #else bool color = true; #endif @@ -99,7 +99,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format, size_t r = 0; #ifdef ARCH_PORTDUINO - bool color = !settingsMap[ascii_logs]; + bool color = !portduino_config.ascii_logs; #else bool color = true; #endif @@ -288,7 +288,7 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) #if ARCH_PORTDUINO // level trace is special, two possible ways to handle it. if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { - if (settingsStrings[traceFilename] != "") { + if (portduino_config.traceFilename != "") { va_list arg; va_start(arg, format); try { @@ -297,18 +297,18 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...) } va_end(arg); } - if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { + if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) { delete[] newFormat; return; } } - if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { + if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) { delete[] newFormat; return; - } else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) { + } else if (portduino_config.logoutputlevel < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) { delete[] newFormat; return; - } else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) { + } else if (portduino_config.logoutputlevel < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) { delete[] newFormat; return; } diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 9ae7ae97d..b23109268 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1422,7 +1422,7 @@ GPS *GPS::createGps() _en_gpio = PIN_GPS_EN; #endif #ifdef ARCH_PORTDUINO - if (!settingsMap[has_gps]) + if (!portduino_config.has_gps) return nullptr; #endif if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 76c423133..3e45bed45 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -370,7 +370,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif ARCH_PORTDUINO if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { - if (settingsMap[displayPanel] != no_screen) { + if (portduino_config.displayPanel != no_screen) { LOG_DEBUG("Make TFTDisplay!"); dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); @@ -618,7 +618,7 @@ void Screen::setup() #if ARCH_PORTDUINO if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { - if (settingsMap[touchscreenModule]) { + if (portduino_config.touchscreenModule) { touchScreenImpl1 = new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index b1814005e..08acc3e55 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -767,24 +767,24 @@ class LGFX : public lgfx::LGFX_Device LGFX(void) { - if (settingsMap[displayPanel] == st7789) + if (portduino_config.displayPanel == st7789) _panel_instance = new lgfx::Panel_ST7789; - else if (settingsMap[displayPanel] == st7735) + else if (portduino_config.displayPanel == st7735) _panel_instance = new lgfx::Panel_ST7735; - else if (settingsMap[displayPanel] == st7735s) + else if (portduino_config.displayPanel == st7735s) _panel_instance = new lgfx::Panel_ST7735S; - else if (settingsMap[displayPanel] == st7796) + else if (portduino_config.displayPanel == st7796) _panel_instance = new lgfx::Panel_ST7796; - else if (settingsMap[displayPanel] == ili9341) + else if (portduino_config.displayPanel == ili9341) _panel_instance = new lgfx::Panel_ILI9341; - else if (settingsMap[displayPanel] == ili9342) + else if (portduino_config.displayPanel == ili9342) _panel_instance = new lgfx::Panel_ILI9342; - else if (settingsMap[displayPanel] == ili9488) + else if (portduino_config.displayPanel == ili9488) _panel_instance = new lgfx::Panel_ILI9488; - else if (settingsMap[displayPanel] == hx8357d) + else if (portduino_config.displayPanel == hx8357d) _panel_instance = new lgfx::Panel_HX8357D; #if defined(LGFX_SDL) - else if (settingsMap[displayPanel] == x11) { + else if (portduino_config.displayPanel == x11) { _panel_instance = new lgfx::Panel_sdl; } #endif @@ -795,61 +795,61 @@ class LGFX : public lgfx::LGFX_Device auto buscfg = _bus_instance.config(); buscfg.spi_mode = 0; - buscfg.spi_host = settingsMap[displayspidev]; + buscfg.spi_host = portduino_config.display_spi_dev_int; - buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable) + buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable) _bus_instance.config(buscfg); // applies the set value to the bus. _panel_instance->setBus(&_bus_instance); // set the bus on the panel. auto cfg = _panel_instance->config(); // Gets a structure for display panel settings. - LOG_DEBUG("Width: %d, Height: %d", settingsMap[displayWidth], settingsMap[displayHeight]); - cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable) - cfg.pin_rst = settingsMap[displayReset]; - if (settingsMap[displayRotate]) { - cfg.panel_width = settingsMap[displayHeight]; // actual displayable width - cfg.panel_height = settingsMap[displayWidth]; // actual displayable height + LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight); + cfg.pin_cs = portduino_config.displayCS.pin; // Pin number where CS is connected (-1 = disable) + cfg.pin_rst = portduino_config.displayReset.pin; + if (portduino_config.displayRotate) { + cfg.panel_width = portduino_config.displayHeight; // actual displayable width + cfg.panel_height = portduino_config.displayWidth; // actual displayable height } else { - cfg.panel_width = settingsMap[displayWidth]; // actual displayable width - cfg.panel_height = settingsMap[displayHeight]; // actual displayable height + cfg.panel_width = portduino_config.displayWidth; // actual displayable width + cfg.panel_height = portduino_config.displayHeight; // actual displayable height } - cfg.offset_x = settingsMap[displayOffsetX]; // Panel offset amount in X direction - cfg.offset_y = settingsMap[displayOffsetY]; // Panel offset amount in Y direction - cfg.offset_rotation = settingsMap[displayOffsetRotate]; // Rotation direction value offset 0~7 (4~7 is mirrored) - cfg.invert = settingsMap[displayInvert]; // Set to true if the light/darkness of the panel is reversed + cfg.offset_x = portduino_config.displayOffsetX; // Panel offset amount in X direction + cfg.offset_y = portduino_config.displayOffsetY; // Panel offset amount in Y direction + cfg.offset_rotation = portduino_config.displayOffsetRotate; // Rotation direction value offset 0~7 (4~7 is mirrored) + cfg.invert = portduino_config.displayInvert; // Set to true if the light/darkness of the panel is reversed _panel_instance->config(cfg); // Configure settings for touch control. - if (settingsMap[touchscreenModule]) { - if (settingsMap[touchscreenModule] == xpt2046) { + if (portduino_config.touchscreenModule) { + if (portduino_config.touchscreenModule == xpt2046) { _touch_instance = new lgfx::Touch_XPT2046; - } else if (settingsMap[touchscreenModule] == stmpe610) { + } else if (portduino_config.touchscreenModule == stmpe610) { _touch_instance = new lgfx::Touch_STMPE610; - } else if (settingsMap[touchscreenModule] == ft5x06) { + } else if (portduino_config.touchscreenModule == ft5x06) { _touch_instance = new lgfx::Touch_FT5x06; } auto touch_cfg = _touch_instance->config(); - touch_cfg.pin_cs = settingsMap[touchscreenCS]; + touch_cfg.pin_cs = portduino_config.touchscreenCS.pin; touch_cfg.x_min = 0; - touch_cfg.x_max = settingsMap[displayHeight] - 1; + touch_cfg.x_max = portduino_config.displayHeight - 1; touch_cfg.y_min = 0; - touch_cfg.y_max = settingsMap[displayWidth] - 1; - touch_cfg.pin_int = settingsMap[touchscreenIRQ]; + touch_cfg.y_max = portduino_config.displayWidth - 1; + touch_cfg.pin_int = portduino_config.touchscreenIRQ.pin; touch_cfg.bus_shared = true; - touch_cfg.offset_rotation = settingsMap[touchscreenRotate]; - if (settingsMap[touchscreenI2CAddr] != -1) { - touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr]; + touch_cfg.offset_rotation = portduino_config.touchscreenRotate; + if (portduino_config.touchscreenI2CAddr != -1) { + touch_cfg.i2c_addr = portduino_config.touchscreenI2CAddr; } else { - touch_cfg.spi_host = settingsMap[touchscreenspidev]; + touch_cfg.spi_host = portduino_config.touchscreen_spi_dev_int; } _touch_instance->config(touch_cfg); _panel_instance->setTouch(_touch_instance); } #if defined(LGFX_SDL) - if (settingsMap[displayPanel] == x11) { + if (portduino_config.displayPanel == x11) { lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance; sdl_panel_->setup(); sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER); @@ -1115,10 +1115,10 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g backlightEnable = p; #if ARCH_PORTDUINO - if (settingsMap[displayRotate]) { - setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]); + if (portduino_config.displayRotate) { + setGeometry(GEOMETRY_RAWMODE, portduino_config.displayWidth, portduino_config.displayWidth); } else { - setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]); + setGeometry(GEOMETRY_RAWMODE, portduino_config.displayHeight, portduino_config.displayHeight); } #elif defined(SCREEN_ROTATE) @@ -1231,7 +1231,7 @@ void TFTDisplay::sdlLoop() #if defined(LGFX_SDL) static int lastPressed = 0; static int shuttingDown = false; - if (settingsMap[displayPanel] == x11) { + if (portduino_config.displayPanel == x11) { lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance; if (sdl_panel_->loop() && !shuttingDown) { LOG_WARN("Window Closed!"); @@ -1279,8 +1279,8 @@ void TFTDisplay::sendCommand(uint8_t com) backlightEnable->set(true); #if ARCH_PORTDUINO display(true); - if (settingsMap[displayBacklight] > 0) - digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON); + if (portduino_config.displayBacklight.pin > 0) + digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->wakeup(); tft->powerSaveOff(); @@ -1303,8 +1303,8 @@ void TFTDisplay::sendCommand(uint8_t com) backlightEnable->set(false); #if ARCH_PORTDUINO tft->clear(); - if (settingsMap[displayBacklight] > 0) - digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON); + if (portduino_config.displayBacklight.pin > 0) + digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON); #elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) tft->sleep(); tft->powerSaveOn(); diff --git a/src/graphics/tftSetup.cpp b/src/graphics/tftSetup.cpp index b2e92bdae..5654fa02a 100644 --- a/src/graphics/tftSetup.cpp +++ b/src/graphics/tftSetup.cpp @@ -41,78 +41,78 @@ void tftSetup(void) PacketAPI::create(PacketServer::init()); deviceScreen->init(new PacketClient); #else - if (settingsMap[displayPanel] != no_screen) { + if (portduino_config.displayPanel != no_screen) { DisplayDriverConfig displayConfig; static char *panels[] = {"NOSCREEN", "X11", "FB", "ST7789", "ST7735", "ST7735S", "ST7796", "ILI9341", "ILI9342", "ILI9486", "ILI9488", "HX8357D"}; static char *touch[] = {"NOTOUCH", "XPT2046", "STMPE610", "GT911", "FT5x06"}; #if defined(USE_X11) - if (settingsMap[displayPanel] == x11) { - if (settingsMap[displayWidth] && settingsMap[displayHeight]) - displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)settingsMap[displayWidth], - (uint16_t)settingsMap[displayHeight]); + if (portduino_config.displayPanel == x11) { + if (portduino_config.displayWidth && portduino_config.displayHeight) + displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)portduino_config.displayWidth, + (uint16_t)portduino_config.displayHeight); else displayConfig.device(DisplayDriverConfig::device_t::X11); } else #elif defined(USE_FRAMEBUFFER) - if (settingsMap[displayPanel] == fb) { - if (settingsMap[displayWidth] && settingsMap[displayHeight]) - displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::FB, (uint16_t)settingsMap[displayWidth], - (uint16_t)settingsMap[displayHeight]); + if (portduino_config.displayPanel == fb) { + if (portduino_config.displayWidth && portduino_config.displayHeight) + displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::FB, (uint16_t)portduino_config.displayWidth, + (uint16_t)portduino_config.displayHeight); else displayConfig.device(DisplayDriverConfig::device_t::FB); } else #endif { displayConfig.device(DisplayDriverConfig::device_t::CUSTOM_TFT) - .panel(DisplayDriverConfig::panel_config_t{.type = panels[settingsMap[displayPanel]], - .panel_width = (uint16_t)settingsMap[displayWidth], - .panel_height = (uint16_t)settingsMap[displayHeight], - .rotation = (bool)settingsMap[displayRotate], - .pin_cs = (int16_t)settingsMap[displayCS], - .pin_rst = (int16_t)settingsMap[displayReset], - .offset_x = (uint16_t)settingsMap[displayOffsetX], - .offset_y = (uint16_t)settingsMap[displayOffsetY], - .offset_rotation = (uint8_t)settingsMap[displayOffsetRotate], - .invert = settingsMap[displayInvert] ? true : false, - .rgb_order = (bool)settingsMap[displayRGBOrder], - .dlen_16bit = settingsMap[displayPanel] == ili9486 || - settingsMap[displayPanel] == ili9488}) - .bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)settingsMap[displayBusFrequency], + .panel(DisplayDriverConfig::panel_config_t{.type = panels[portduino_config.displayPanel], + .panel_width = (uint16_t)portduino_config.displayWidth, + .panel_height = (uint16_t)portduino_config.displayHeight, + .rotation = (bool)portduino_config.displayRotate, + .pin_cs = (int16_t)portduino_config.displayCS.pin, + .pin_rst = (int16_t)portduino_config.displayReset.pin, + .offset_x = (uint16_t)portduino_config.displayOffsetX, + .offset_y = (uint16_t)portduino_config.displayOffsetY, + .offset_rotation = (uint8_t)portduino_config.displayOffsetRotate, + .invert = portduino_config.displayInvert ? true : false, + .rgb_order = (bool)portduino_config.displayRGBOrder, + .dlen_16bit = portduino_config.displayPanel == ili9486 || + portduino_config.displayPanel == ili9488}) + .bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)portduino_config.displayBusFrequency, .freq_read = 16000000, - .spi{.pin_dc = (int8_t)settingsMap[displayDC], + .spi{.pin_dc = (int8_t)portduino_config.displayDC.pin, .use_lock = true, - .spi_host = (uint16_t)settingsMap[displayspidev]}}) - .input(DisplayDriverConfig::input_config_t{.keyboardDevice = settingsStrings[keyboardDevice], - .pointerDevice = settingsStrings[pointerDevice]}) - .light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)settingsMap[displayBacklight], - .pwm_channel = (int8_t)settingsMap[displayBacklightPWMChannel], - .invert = (bool)settingsMap[displayBacklightInvert]}); - if (settingsMap[touchscreenI2CAddr] == -1) { + .spi_host = (uint16_t)portduino_config.display_spi_dev_int}}) + .input(DisplayDriverConfig::input_config_t{.keyboardDevice = portduino_config.keyboardDevice, + .pointerDevice = portduino_config.pointerDevice}) + .light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)portduino_config.displayBacklight.pin, + .pwm_channel = (int8_t)portduino_config.displayBacklightPWMChannel.pin, + .invert = (bool)portduino_config.displayBacklightInvert}); + if (portduino_config.touchscreenI2CAddr == -1) { displayConfig.touch( - DisplayDriverConfig::touch_config_t{.type = touch[settingsMap[touchscreenModule]], - .freq = (uint32_t)settingsMap[touchscreenBusFrequency], - .pin_int = (int16_t)settingsMap[touchscreenIRQ], - .offset_rotation = (uint8_t)settingsMap[touchscreenRotate], + DisplayDriverConfig::touch_config_t{.type = touch[portduino_config.touchscreenModule], + .freq = (uint32_t)portduino_config.touchscreenBusFrequency, + .pin_int = (int16_t)portduino_config.touchscreenIRQ.pin, + .offset_rotation = (uint8_t)portduino_config.touchscreenRotate, .spi{ - .spi_host = (int8_t)settingsMap[touchscreenspidev], + .spi_host = (int8_t)portduino_config.touchscreen_spi_dev_int, }, - .pin_cs = (int16_t)settingsMap[touchscreenCS]}); + .pin_cs = (int16_t)portduino_config.touchscreenCS.pin}); } else { displayConfig.touch(DisplayDriverConfig::touch_config_t{ - .type = touch[settingsMap[touchscreenModule]], - .freq = (uint32_t)settingsMap[touchscreenBusFrequency], + .type = touch[portduino_config.touchscreenModule], + .freq = (uint32_t)portduino_config.touchscreenBusFrequency, .x_min = 0, - .x_max = - (int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayWidth] : settingsMap[displayHeight]) - - 1), + .x_max = (int16_t)((portduino_config.touchscreenRotate & 1 ? portduino_config.displayWidth + : portduino_config.displayHeight) - + 1), .y_min = 0, - .y_max = - (int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayHeight] : settingsMap[displayWidth]) - - 1), - .pin_int = (int16_t)settingsMap[touchscreenIRQ], - .offset_rotation = (uint8_t)settingsMap[touchscreenRotate], - .i2c{.i2c_addr = (uint8_t)settingsMap[touchscreenI2CAddr]}}); + .y_max = (int16_t)((portduino_config.touchscreenRotate & 1 ? portduino_config.displayHeight + : portduino_config.displayWidth) - + 1), + .pin_int = (int16_t)portduino_config.touchscreenIRQ.pin, + .offset_rotation = (uint8_t)portduino_config.touchscreenRotate, + .i2c{.i2c_addr = (uint8_t)portduino_config.touchscreenI2CAddr}}); } } deviceScreen = &DeviceScreen::create(&displayConfig); diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp index 90f06ecc9..1f80fd5d3 100644 --- a/src/input/LinuxInput.cpp +++ b/src/input/LinuxInput.cpp @@ -33,9 +33,9 @@ int32_t LinuxInput::runOnce() { if (firstTime) { - if (settingsStrings[keyboardDevice] == "") + if (portduino_config.keyboardDevice == "") return disable(); - fd = open(settingsStrings[keyboardDevice].c_str(), O_RDWR); + fd = open(portduino_config.keyboardDevice.c_str(), O_RDWR); if (fd < 0) return disable(); ret = ioctl(fd, EVIOCGRAB, (void *)1); diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp index cea47faeb..c0e220941 100644 --- a/src/input/TouchScreenImpl1.cpp +++ b/src/input/TouchScreenImpl1.cpp @@ -18,7 +18,7 @@ TouchScreenImpl1::TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTo void TouchScreenImpl1::init() { #if ARCH_PORTDUINO - if (settingsMap[touchscreenModule]) { + if (portduino_config.touchscreenModule) { TouchScreenBase::init(true); inputBroker->registerSource(this); } else { diff --git a/src/input/TrackballInterruptBase.h b/src/input/TrackballInterruptBase.h index 38be22f20..76a99f33d 100644 --- a/src/input/TrackballInterruptBase.h +++ b/src/input/TrackballInterruptBase.h @@ -6,7 +6,7 @@ #ifndef TB_DIRECTION #if ARCH_PORTDUINO #include "PortduinoGlue.h" -#define TB_DIRECTION (PinStatus) settingsMap[tbDirection] +#define TB_DIRECTION (PinStatus) portduino_config.lora_usb_vid #else #define TB_DIRECTION RISING #endif diff --git a/src/main.cpp b/src/main.cpp index 73f68e95e..5b223a824 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -390,7 +390,7 @@ void setup() concurrency::hasBeenSetup = true; #if ARCH_PORTDUINO - SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0); + SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); #else SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); #endif @@ -535,9 +535,9 @@ void setup() #elif defined(I2C_SDA) && !defined(ARCH_RP2040) Wire.begin(I2C_SDA, I2C_SCL); #elif defined(ARCH_PORTDUINO) - if (settingsStrings[i2cdev] != "") { - LOG_INFO("Use %s as I2C device", settingsStrings[i2cdev].c_str()); - Wire.begin(settingsStrings[i2cdev].c_str()); + if (portduino_config.i2cdev != "") { + LOG_INFO("Use %s as I2C device", portduino_config.i2cdev.c_str()); + Wire.begin(portduino_config.i2cdev.c_str()); } else { LOG_INFO("No I2C device configured, Skip"); } @@ -583,7 +583,7 @@ void setup() #if defined(I2C_SDA) i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); #elif defined(ARCH_PORTDUINO) - if (settingsStrings[i2cdev] != "") { + if (portduino_config.i2cdev != "") { LOG_INFO("Scan for i2c devices"); i2cScanner->scanPort(ScanI2C::I2CPort::WIRE); } @@ -855,7 +855,7 @@ void setup() SPI.begin(false); #endif // HW_SPI1_DEVICE #elif ARCH_PORTDUINO - if (settingsStrings[spidev] != "ch341") { + if (portduino_config.lora_spi_dev != "ch341") { SPI.begin(); } #elif !defined(ARCH_ESP32) // ARCH_RP2040 @@ -881,7 +881,7 @@ void setup() defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) screen = new graphics::Screen(screen_found, screen_model, screen_geometry); #elif defined(ARCH_PORTDUINO) - if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) && + if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) && config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { screen = new graphics::Screen(screen_found, screen_model, screen_geometry); } @@ -982,13 +982,13 @@ void setup() #endif #if defined(ARCH_PORTDUINO) - if (settingsMap.count(userButtonPin) != 0 && settingsMap[userButtonPin] != RADIOLIB_NC) { + if (portduino_config.userButtonPin.enabled) { - LOG_DEBUG("Use GPIO%02d for button", settingsMap[userButtonPin]); + LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin); UserButtonThread = new ButtonThread("UserButton"); if (screen) { ButtonConfig config; - config.pinNumber = (uint8_t)settingsMap[userButtonPin]; + config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin; config.activeLow = true; config.activePullup = true; config.pullupSense = INPUT_PULLUP; @@ -1145,7 +1145,7 @@ void setup() if (screen) screen->setup(); #elif defined(ARCH_PORTDUINO) - if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) && + if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) && config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { screen->setup(); } @@ -1161,15 +1161,10 @@ void setup() #endif #ifdef ARCH_PORTDUINO - const struct { - configNames cfgName; - std::string strName; - } loraModules[] = {{use_rf95, "RF95"}, {use_sx1262, "sx1262"}, {use_sx1268, "sx1268"}, {use_sx1280, "sx1280"}, - {use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}}; // as one can't use a function pointer to the class constructor: - auto loraModuleInterface = [](configNames cfgName, LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, - RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) { - switch (cfgName) { + 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: @@ -1186,31 +1181,34 @@ void setup() 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; } }; - for (auto &loraModule : loraModules) { - if (settingsMap[loraModule.cfgName] && !rIf) { - LOG_DEBUG("Activate %s radio on SPI port %s", loraModule.strName.c_str(), settingsStrings[spidev].c_str()); - if (settingsStrings[spidev] == "ch341") { - RadioLibHAL = ch341Hal; - } else { - RadioLibHAL = new LockingArduinoHal(SPI, spiSettings); - } - rIf = loraModuleInterface(loraModule.cfgName, (LockingArduinoHal *)RadioLibHAL, settingsMap[cs_pin], - settingsMap[irq_pin], settingsMap[reset_pin], settingsMap[busy_pin]); - if (!rIf->init()) { - LOG_WARN("No %s radio", loraModule.strName.c_str()); - delete rIf; - rIf = NULL; - exit(EXIT_FAILURE); - } else { - LOG_INFO("%s init success", loraModule.strName.c_str()); - } - } + + 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 @@ -1232,20 +1230,6 @@ void setup() } #endif -#if defined(ARCH_PORTDUINO) - if (!rIf) { - rIf = new SimRadio; - if (!rIf->init()) { - LOG_WARN("No simulated radio"); - delete rIf; - rIf = NULL; - } else { - LOG_INFO("Use SIMULATED radio!"); - radioType = SIM_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); @@ -1460,7 +1444,7 @@ void setup() #ifdef ARCH_PORTDUINO #if __has_include() - if (settingsMap[webserverport] != -1) { + if (portduino_config.webserverport != -1) { piwebServerThread = new PiWebServerThread(); std::atexit([] { delete piwebServerThread; }); } diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index a0d992c42..f83522c8b 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -21,7 +21,7 @@ static const Module::RfSwitchMode_t rfswitch_table[] = { // Particular boards might define a different max power based on what their hardware can do, default to max power output if not // specified (may be dangerous if using external PA and LR11x0 power config forgotten) #if ARCH_PORTDUINO -#define LR1110_MAX_POWER settingsMap[lr1110_max_power] +#define LR1110_MAX_POWER portduino_config.lr1110_max_power #endif #ifndef LR1110_MAX_POWER #define LR1110_MAX_POWER 22 @@ -30,7 +30,7 @@ static const Module::RfSwitchMode_t rfswitch_table[] = { // the 2.4G part maxes at 13dBm #if ARCH_PORTDUINO -#define LR1120_MAX_POWER settingsMap[lr1120_max_power] +#define LR1120_MAX_POWER portduino_config.lr1120_max_power #endif #ifndef LR1120_MAX_POWER #define LR1120_MAX_POWER 13 @@ -55,7 +55,7 @@ template bool LR11x0Interface::init() #endif #if ARCH_PORTDUINO - float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000; + float tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000; // FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE #elif !defined(LR11X0_DIO3_TCXO_VOLTAGE) float tcxoVoltage = diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index c8eba1b2e..2ab6fda59 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -673,7 +673,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) #endif #elif ARCH_PORTDUINO bool hasScreen = false; - if (settingsMap[displayPanel]) + if (portduino_config.displayPanel) hasScreen = true; else hasScreen = screen_found.port != ScanI2C::I2CPort::NO_I2C; @@ -1334,8 +1334,8 @@ void NodeDB::loadFromDisk() } #if ARCH_PORTDUINO // set any config overrides - if (settingsMap[has_configDisplayMode]) { - config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)settingsMap[configDisplayMode]; + if (portduino_config.has_configDisplayMode) { + config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode; } #endif diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 97f21fc34..0f32f3427 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -10,7 +10,7 @@ #endif #if ARCH_PORTDUINO -#define RF95_MAX_POWER settingsMap[rf95_max_power] +#define RF95_MAX_POWER portduino_config.rf95_max_power #endif #ifndef RF95_MAX_POWER #define RF95_MAX_POWER 20 @@ -94,16 +94,16 @@ void RF95Interface::setTransmitEnable(bool txon) #ifdef RF95_TXEN digitalWrite(RF95_TXEN, txon ? 1 : 0); #elif ARCH_PORTDUINO - if (settingsMap[txen_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[txen_pin], txon ? 1 : 0); + if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_txen_pin.pin, txon ? 1 : 0); } #endif #ifdef RF95_RXEN digitalWrite(RF95_RXEN, txon ? 0 : 1); #elif ARCH_PORTDUINO - if (settingsMap[rxen_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[rxen_pin], txon ? 0 : 1); + if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_rxen_pin.pin, txon ? 0 : 1); } #endif } @@ -164,13 +164,13 @@ bool RF95Interface::init() digitalWrite(RF95_RXEN, 1); #endif #if ARCH_PORTDUINO - if (settingsMap[txen_pin] != RADIOLIB_NC) { - pinMode(settingsMap[txen_pin], OUTPUT); - digitalWrite(settingsMap[txen_pin], 0); + if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) { + pinMode(portduino_config.lora_txen_pin.pin, OUTPUT); + digitalWrite(portduino_config.lora_txen_pin.pin, 0); } - if (settingsMap[rxen_pin] != RADIOLIB_NC) { - pinMode(settingsMap[rxen_pin], OUTPUT); - digitalWrite(settingsMap[rxen_pin], 0); + if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) { + pinMode(portduino_config.lora_rxen_pin.pin, OUTPUT); + digitalWrite(portduino_config.lora_rxen_pin.pin, 0); } #endif setTransmitEnable(false); diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 946b1982c..c18612101 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -417,7 +417,7 @@ void RadioLibInterface::handleReceiveInterrupt() int state = iface->readData((uint8_t *)&radioBuffer, length); #if ARCH_PORTDUINO - if (settingsMap[logoutputlevel] == level_trace) { + if (portduino_config.logoutputlevel == level_trace) { printBytes("Raw incoming packet: ", (uint8_t *)&radioBuffer, length); } #endif diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index c7e32c4a1..221e0275b 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -446,7 +446,7 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p) #if ENABLE_JSON_LOGGING LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str()); #elif ARCH_PORTDUINO - if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { + if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) { LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str()); } #endif @@ -685,7 +685,7 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p) LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str()); #elif ARCH_PORTDUINO // Even ignored packets get logged in the trace - if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) { + if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) { p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str()); } diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 729c1abc6..49dc562d4 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -12,7 +12,7 @@ // Particular boards might define a different max power based on what their hardware can do, default to max power output if not // specified (may be dangerous if using external PA and SX126x power config forgotten) #if ARCH_PORTDUINO -#define SX126X_MAX_POWER settingsMap[sx126x_max_power] +#define SX126X_MAX_POWER portduino_config.sx126x_max_power #endif #ifndef SX126X_MAX_POWER #define SX126X_MAX_POWER 22 @@ -53,10 +53,10 @@ template bool SX126xInterface::init() #endif #if ARCH_PORTDUINO - tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000; - if (settingsMap[sx126x_ant_sw_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[sx126x_ant_sw_pin], HIGH); - pinMode(settingsMap[sx126x_ant_sw_pin], OUTPUT); + tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000; + if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_sx126x_ant_sw_pin.pin, HIGH); + pinMode(portduino_config.lora_sx126x_ant_sw_pin.pin, OUTPUT); } #endif if (tcxoVoltage == 0.0) @@ -98,7 +98,7 @@ template bool SX126xInterface::init() bool dio2AsRfSwitch = true; #elif defined(ARCH_PORTDUINO) bool dio2AsRfSwitch = false; - if (settingsMap[dio2_as_rf_switch]) { + if (portduino_config.dio2_as_rf_switch) { dio2AsRfSwitch = true; } #else @@ -112,9 +112,9 @@ template bool SX126xInterface::init() // no effect #if ARCH_PORTDUINO if (res == RADIOLIB_ERR_NONE) { - LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", settingsMap[rxen_pin], - settingsMap[txen_pin]); - lora.setRfSwitchPins(settingsMap[rxen_pin], settingsMap[txen_pin]); + LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", portduino_config.lora_rxen_pin.pin, + portduino_config.lora_txen_pin.pin); + lora.setRfSwitchPins(portduino_config.lora_rxen_pin.pin, portduino_config.lora_txen_pin.pin); } #else #ifndef SX126X_RXEN diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 866426872..cbc98eeb1 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -11,7 +11,7 @@ // Particular boards might define a different max power based on what their hardware can do #if ARCH_PORTDUINO -#define SX128X_MAX_POWER settingsMap[sx128x_max_power] +#define SX128X_MAX_POWER portduino_config.sx128x_max_power #endif #ifndef SX128X_MAX_POWER #define SX128X_MAX_POWER 13 @@ -41,13 +41,13 @@ template bool SX128xInterface::init() #endif #if ARCH_PORTDUINO - if (settingsMap[rxen_pin] != RADIOLIB_NC) { - pinMode(settingsMap[rxen_pin], OUTPUT); - digitalWrite(settingsMap[rxen_pin], LOW); // Set low before becoming an output + if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) { + pinMode(portduino_config.lora_rxen_pin.pin, OUTPUT); + digitalWrite(portduino_config.lora_rxen_pin.pin, LOW); // Set low before becoming an output } - if (settingsMap[txen_pin] != RADIOLIB_NC) { - pinMode(settingsMap[txen_pin], OUTPUT); - digitalWrite(settingsMap[txen_pin], LOW); // Set low before becoming an output + if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) { + pinMode(portduino_config.lora_txen_pin.pin, OUTPUT); + digitalWrite(portduino_config.lora_txen_pin.pin, LOW); // Set low before becoming an output } #else #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode @@ -93,8 +93,9 @@ template bool SX128xInterface::init() lora.setRfSwitchPins(SX128X_RXEN, SX128X_TXEN); } #elif ARCH_PORTDUINO - if (res == RADIOLIB_ERR_NONE && settingsMap[rxen_pin] != RADIOLIB_NC && settingsMap[txen_pin] != RADIOLIB_NC) { - lora.setRfSwitchPins(settingsMap[rxen_pin], settingsMap[txen_pin]); + if (res == RADIOLIB_ERR_NONE && portduino_config.lora_rxen_pin.pin != RADIOLIB_NC && + portduino_config.lora_txen_pin.pin != RADIOLIB_NC) { + lora.setRfSwitchPins(portduino_config.lora_rxen_pin.pin, portduino_config.lora_txen_pin.pin); } #endif @@ -174,11 +175,11 @@ template void SX128xInterface::setStandby() LOG_ERROR("SX128x standby %s%d", radioLibErr, err); assert(err == RADIOLIB_ERR_NONE); #if ARCH_PORTDUINO - if (settingsMap[rxen_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[rxen_pin], LOW); + if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_rxen_pin.pin, LOW); } - if (settingsMap[txen_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[txen_pin], LOW); + if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_txen_pin.pin, LOW); } #else #if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power @@ -210,11 +211,11 @@ template void SX128xInterface::addReceiveMetadata(meshtastic_Mes template void SX128xInterface::configHardwareForSend() { #if ARCH_PORTDUINO - if (settingsMap[txen_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[txen_pin], HIGH); + if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_txen_pin.pin, HIGH); } - if (settingsMap[rxen_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[rxen_pin], LOW); + if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_rxen_pin.pin, LOW); } #else @@ -241,11 +242,11 @@ template void SX128xInterface::startReceive() setStandby(); #if ARCH_PORTDUINO - if (settingsMap[rxen_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[rxen_pin], HIGH); + if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_rxen_pin.pin, HIGH); } - if (settingsMap[txen_pin] != RADIOLIB_NC) { - digitalWrite(settingsMap[txen_pin], LOW); + if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) { + digitalWrite(portduino_config.lora_txen_pin.pin, LOW); } #else diff --git a/src/mesh/raspihttp/PiWebServer.cpp b/src/mesh/raspihttp/PiWebServer.cpp index 7d3542e83..3e9dbe8c2 100644 --- a/src/mesh/raspihttp/PiWebServer.cpp +++ b/src/mesh/raspihttp/PiWebServer.cpp @@ -65,8 +65,8 @@ mail: marchammermann@googlemail.com #define DEFAULT_REALM "default_realm" #define PREFIX "" -#define KEY_PATH settingsStrings[websslkeypath].c_str() -#define CERT_PATH settingsStrings[websslcertpath].c_str() +#define KEY_PATH portduino_config.webserver_ssl_key_path.c_str() +#define CERT_PATH portduino_config.webserver_ssl_cert_path.c_str() struct _file_config configWeb; @@ -458,8 +458,8 @@ PiWebServerThread::PiWebServerThread() } } - if (settingsMap[webserverport] != 0) { - webservport = settingsMap[webserverport]; + if (portduino_config.webserverport != 0) { + webservport = portduino_config.webserverport; LOG_INFO("Use webserver port from yaml config %i ", webservport); } else { LOG_INFO("Webserver port in yaml config set to 0, defaulting to port 9443"); @@ -490,7 +490,7 @@ PiWebServerThread::PiWebServerThread() u_map_put(&configWeb.mime_types, ".ico", "image/x-icon"); u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml"); - webrootpath = settingsStrings[webserverrootpath]; + webrootpath = portduino_config.webserver_root_path; configWeb.files_path = (char *)webrootpath.c_str(); configWeb.url_prefix = ""; diff --git a/src/modules/Telemetry/HostMetrics.cpp b/src/modules/Telemetry/HostMetrics.cpp index 8f10b9228..dcde495a2 100644 --- a/src/modules/Telemetry/HostMetrics.cpp +++ b/src/modules/Telemetry/HostMetrics.cpp @@ -9,11 +9,11 @@ int32_t HostMetricsModule::runOnce() { #if ARCH_PORTDUINO - if (settingsMap[hostMetrics_interval] == 0) { + if (portduino_config.hostMetrics_interval == 0) { return disable(); } else { sendMetrics(); - return 60 * 1000 * settingsMap[hostMetrics_interval]; + return 60 * 1000 * portduino_config.hostMetrics_interval; } #else return disable(); @@ -110,8 +110,8 @@ meshtastic_Telemetry HostMetricsModule::getHostMetrics() proc_loadavg.close(); } } - if (settingsStrings[hostMetrics_user_command] != "") { - std::string userCommandResult = exec(settingsStrings[hostMetrics_user_command].c_str()); + if (portduino_config.hostMetrics_user_command != "") { + std::string userCommandResult = exec(portduino_config.hostMetrics_user_command.c_str()); if (userCommandResult.length() > 1) { strncpy(t.variant.host_metrics.user_string, userCommandResult.c_str(), sizeof(t.variant.host_metrics.user_string)); t.variant.host_metrics.user_string[sizeof(t.variant.host_metrics.user_string) - 1] = '\0'; @@ -135,7 +135,7 @@ bool HostMetricsModule::sendMetrics() p->to = NODENUM_BROADCAST; p->decoded.want_response = false; p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - p->channel = settingsMap[hostMetrics_channel]; + p->channel = portduino_config.hostMetrics_channel; LOG_INFO("Send packet to mesh"); service->sendToMesh(p, RX_SRC_LOCAL, true); return true; diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 929a45d09..b11d2547b 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -9,7 +9,6 @@ #include "api/ServerAPI.h" #include "linux/gpio/LinuxGPIOPin.h" #include "meshUtils.h" -#include "yaml-cpp/yaml.h" #include #include #include @@ -28,14 +27,13 @@ #include "platform/portduino/USBHal.h" -std::map settingsMap; -std::map settingsStrings; portduino_config_struct portduino_config; std::ofstream traceFile; Ch341Hal *ch341Hal = nullptr; char *configPath = nullptr; char *optionMac = nullptr; bool verboseEnabled = false; +bool yamlOnly = false; const char *argp_program_version = optstr(APP_VERSION); @@ -75,6 +73,9 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) case 'v': verboseEnabled = true; break; + case 'y': + yamlOnly = true; + break; case ARGP_KEY_ARG: return 0; default: @@ -90,6 +91,7 @@ void portduinoCustomInit() {"hwid", 'h', "HWID", 0, "The mac address to assign to this virtual machine"}, {"sim", 's', 0, 0, "Run in Simulated radio mode"}, {"verbose", 'v', 0, 0, "Set log level to full debug"}, + {"output-yaml", 'y', 0, 0, "Output config yaml and exit"}, {0}}; static void *childArguments; static char doc[] = "Meshtastic native build."; @@ -115,8 +117,8 @@ void getMacAddr(uint8_t *dmac) dmac[4] = hwId >> 8; dmac[5] = hwId & 0xff; } - } else if (settingsStrings[mac_address].length() > 11) { - MAC_from_string(settingsStrings[mac_address], dmac); + } else if (portduino_config.mac_address.length() > 11) { + MAC_from_string(portduino_config.mac_address, dmac); exit; } else { @@ -148,89 +150,46 @@ void getMacAddr(uint8_t *dmac) */ void portduinoSetup() { - printf("Set up Meshtastic on Portduino...\n"); int max_GPIO = 0; - const configNames GPIO_lines[] = {cs_pin, - irq_pin, - busy_pin, - reset_pin, - sx126x_ant_sw_pin, - txen_pin, - rxen_pin, - displayDC, - displayCS, - displayBacklight, - displayBacklightPWMChannel, - displayReset, - touchscreenCS, - touchscreenIRQ, - userButtonPin, - tbUpPin, - tbDownPin, - tbLeftPin, - tbRightPin, - tbPressPin}; - std::string gpioChipName = "gpiochip"; - settingsStrings[i2cdev] = ""; - settingsStrings[keyboardDevice] = ""; - settingsStrings[pointerDevice] = ""; - settingsStrings[webserverrootpath] = ""; - settingsStrings[spidev] = ""; - settingsStrings[displayspidev] = ""; - settingsMap[spiSpeed] = 2000000; - settingsMap[ascii_logs] = !isatty(1); - settingsMap[displayPanel] = no_screen; - settingsMap[touchscreenModule] = no_touchscreen; - settingsMap[tbUpPin] = RADIOLIB_NC; - settingsMap[tbDownPin] = RADIOLIB_NC; - settingsMap[tbLeftPin] = RADIOLIB_NC; - settingsMap[tbRightPin] = RADIOLIB_NC; - settingsMap[tbPressPin] = RADIOLIB_NC; - - YAML::Node yamlConfig; + portduino_config.displayPanel = no_screen; if (portduino_config.force_simradio == true) { - settingsMap[use_simradio] = true; + portduino_config.lora_module = use_simradio; } else if (configPath != nullptr) { if (loadConfig(configPath)) { - std::cout << "Using " << configPath << " as config file" << std::endl; + if (!yamlOnly) + std::cout << "Using " << configPath << " as config file" << std::endl; } else { std::cout << "Unable to use " << configPath << " as config file" << std::endl; exit(EXIT_FAILURE); } } else if (access("config.yaml", R_OK) == 0) { if (loadConfig("config.yaml")) { - std::cout << "Using local config.yaml as config file" << std::endl; + if (!yamlOnly) + std::cout << "Using local config.yaml as config file" << std::endl; } else { std::cout << "Unable to use local config.yaml as config file" << std::endl; exit(EXIT_FAILURE); } } else if (access("/etc/meshtasticd/config.yaml", R_OK) == 0) { if (loadConfig("/etc/meshtasticd/config.yaml")) { - std::cout << "Using /etc/meshtasticd/config.yaml as config file" << std::endl; + if (!yamlOnly) + std::cout << "Using /etc/meshtasticd/config.yaml as config file" << std::endl; } else { std::cout << "Unable to use /etc/meshtasticd/config.yaml as config file" << std::endl; exit(EXIT_FAILURE); } } else { - std::cout << "No 'config.yaml' found..." << std::endl; - settingsMap[use_simradio] = true; + if (!yamlOnly) + std::cout << "No 'config.yaml' found..." << std::endl; + portduino_config.lora_module = use_simradio; } - if (settingsMap[use_simradio] == true) { - std::cout << "Running in simulated mode." << std::endl; - settingsMap[maxnodes] = 200; // Default to 200 nodes - settingsMap[logoutputlevel] = level_debug; // Default to debug - // Set the random seed equal to TCPPort to have a different seed per instance - randomSeed(TCPPort); - return; - } - - if (settingsStrings[config_directory] != "") { + if (portduino_config.config_directory != "") { std::string filetype = ".yaml"; for (const std::filesystem::directory_entry &entry : - std::filesystem::directory_iterator{settingsStrings[config_directory]}) { + std::filesystem::directory_iterator{portduino_config.config_directory}) { if (ends_with(entry.path().string(), ".yaml")) { std::cout << "Also using " << entry << " as additional config file" << std::endl; loadConfig(entry.path().c_str()); @@ -238,15 +197,28 @@ void portduinoSetup() } } + if (yamlOnly) { + std::cout << portduino_config.emit_yaml() << std::endl; + exit(EXIT_SUCCESS); + } + + if (portduino_config.lora_module == use_simradio) { + std::cout << "Running in simulated mode." << std::endl; + portduino_config.MaxNodes = 200; // Default to 200 nodes + // Set the random seed equal to TCPPort to have a different seed per instance + randomSeed(TCPPort); + return; + } + // If LoRa `Module: auto` (default in config.yaml), // attempt to auto config based on Product Strings - if (settingsMap[use_autoconf] == true) { + if (portduino_config.lora_module == use_autoconf) { char autoconf_product[96] = {0}; // Try CH341 try { std::cout << "autoconf: Looking for CH341 device..." << std::endl; - ch341Hal = - new Ch341Hal(0, settingsStrings[lora_usb_serial_num], settingsMap[lora_usb_vid], settingsMap[lora_usb_pid]); + ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid, + portduino_config.lora_usb_pid); ch341Hal->getProductString(autoconf_product, 95); delete ch341Hal; std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl; @@ -323,7 +295,7 @@ void portduinoSetup() if (mac_start != nullptr) { std::cout << "autoconf: Found mac data " << mac_start << std::endl; if (strlen(mac_start) == 12) - settingsStrings[mac_address] = std::string(mac_start); + portduino_config.mac_address = std::string(mac_start); } if (devID_start != nullptr) { std::cout << "autoconf: Found deviceid data " << devID_start << std::endl; @@ -354,7 +326,7 @@ void portduinoSetup() std::cerr << "autoconf: Unable to find config for " << autoconf_product << std::endl; exit(EXIT_FAILURE); } - if (loadConfig((settingsStrings[available_directory] + product_config).c_str())) { + if (loadConfig((portduino_config.available_directory + product_config).c_str())) { std::cout << "autoconf: Using " << product_config << " as config file for " << autoconf_product << std::endl; } else { std::cerr << "autoconf: Unable to use " << product_config << " as config file for " << autoconf_product @@ -363,15 +335,16 @@ void portduinoSetup() } } else { std::cerr << "autoconf: Could not locate any devices" << std::endl; + exit(EXIT_FAILURE); } } // if we're using a usermode driver, we need to initialize it here, to get a serial number back for mac address uint8_t dmac[6] = {0}; - if (settingsStrings[spidev] == "ch341") { + if (portduino_config.lora_spi_dev == "ch341") { try { - ch341Hal = - new Ch341Hal(0, settingsStrings[lora_usb_serial_num], settingsMap[lora_usb_vid], settingsMap[lora_usb_pid]); + 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; @@ -383,7 +356,7 @@ void portduinoSetup() char product_string[96] = {0}; ch341Hal->getProductString(product_string, 95); std::cout << "CH341 Product " << product_string << std::endl; - if (strlen(serial) == 8 && settingsStrings[mac_address].length() < 12) { + if (strlen(serial) == 8 && portduino_config.mac_address.length() < 12) { uint8_t hash[32] = {0}; memcpy(hash, serial, 8); crypto->hash(hash, 8); @@ -395,7 +368,7 @@ void portduinoSetup() dmac[5] = hash[5]; char macBuf[13] = {0}; sprintf(macBuf, "%02X%02X%02X%02X%02X%02X", dmac[0], dmac[1], dmac[2], dmac[3], dmac[4], dmac[5]); - settingsStrings[mac_address] = macBuf; + portduino_config.mac_address = macBuf; } } @@ -409,100 +382,38 @@ void portduinoSetup() // Rather important to set this, if not running simulated. randomSeed(time(NULL)); - std::string defaultGpioChipName = gpioChipName + std::to_string(settingsMap[default_gpiochip]); - - for (configNames i : GPIO_lines) { - if (settingsMap.count(i) && settingsMap[i] > max_GPIO) - max_GPIO = settingsMap[i]; + std::string defaultGpioChipName = gpioChipName + std::to_string(portduino_config.lora_default_gpiochip); + for (auto i : portduino_config.all_pins) { + if (i->enabled && i->pin > max_GPIO) + max_GPIO = i->pin; } gpioInit(max_GPIO + 1); // Done here so we can inform Portduino how many GPIOs we need. // Need to bind all the configured GPIO pins so they're not simulated // TODO: If one of these fails, we should log and terminate - if (settingsMap.count(userButtonPin) > 0 && settingsMap[userButtonPin] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[userButtonPin], defaultGpioChipName, settingsMap[userButtonPin]) != ERRNO_OK) { - settingsMap[userButtonPin] = RADIOLIB_NC; - } - } - if (settingsMap.count(tbUpPin) > 0 && settingsMap[tbUpPin] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[tbUpPin], defaultGpioChipName, settingsMap[tbUpPin]) != ERRNO_OK) { - settingsMap[tbUpPin] = RADIOLIB_NC; - } - } - if (settingsMap.count(tbDownPin) > 0 && settingsMap[tbDownPin] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[tbDownPin], defaultGpioChipName, settingsMap[tbDownPin]) != ERRNO_OK) { - settingsMap[tbDownPin] = RADIOLIB_NC; - } - } - if (settingsMap.count(tbLeftPin) > 0 && settingsMap[tbLeftPin] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[tbLeftPin], defaultGpioChipName, settingsMap[tbLeftPin]) != ERRNO_OK) { - settingsMap[tbLeftPin] = RADIOLIB_NC; - } - } - if (settingsMap.count(tbRightPin) > 0 && settingsMap[tbRightPin] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[tbRightPin], defaultGpioChipName, settingsMap[tbRightPin]) != ERRNO_OK) { - settingsMap[tbRightPin] = RADIOLIB_NC; - } - } - if (settingsMap.count(tbPressPin) > 0 && settingsMap[tbPressPin] != RADIOLIB_NC) { - if (initGPIOPin(settingsMap[tbPressPin], defaultGpioChipName, settingsMap[tbPressPin]) != ERRNO_OK) { - settingsMap[tbPressPin] = RADIOLIB_NC; - } - } - if (settingsMap[displayPanel] != no_screen) { - if (settingsMap[displayCS] > 0) - initGPIOPin(settingsMap[displayCS], defaultGpioChipName, settingsMap[displayCS]); - if (settingsMap[displayDC] > 0) - initGPIOPin(settingsMap[displayDC], defaultGpioChipName, settingsMap[displayDC]); - if (settingsMap[displayBacklight] > 0) - initGPIOPin(settingsMap[displayBacklight], defaultGpioChipName, settingsMap[displayBacklight]); - if (settingsMap[displayReset] > 0) - initGPIOPin(settingsMap[displayReset], defaultGpioChipName, settingsMap[displayReset]); - } - if (settingsMap[touchscreenModule] != no_touchscreen) { - if (settingsMap[touchscreenCS] > 0) - initGPIOPin(settingsMap[touchscreenCS], defaultGpioChipName, settingsMap[touchscreenCS]); - if (settingsMap[touchscreenIRQ] > 0) - initGPIOPin(settingsMap[touchscreenIRQ], defaultGpioChipName, settingsMap[touchscreenIRQ]); + for (auto i : portduino_config.all_pins) { + if (i->enabled) + if (initGPIOPin(i->pin, gpioChipName + std::to_string(i->gpiochip), i->line) != ERRNO_OK) { + printf("Error setting pin number %d. It may not exist, or may already be in use.\n", i->line); + exit(EXIT_FAILURE); + } } // Only initialize the radio pins when dealing with real, kernel controlled SPI hardware - if (settingsStrings[spidev] != "" && settingsStrings[spidev] != "ch341") { - const struct { - configNames pin; - configNames gpiochip; - configNames line; - } pinMappings[] = {{cs_pin, cs_gpiochip, cs_line}, - {irq_pin, irq_gpiochip, irq_line}, - {busy_pin, busy_gpiochip, busy_line}, - {reset_pin, reset_gpiochip, reset_line}, - {rxen_pin, rxen_gpiochip, rxen_line}, - {txen_pin, txen_gpiochip, txen_line}, - {sx126x_ant_sw_pin, sx126x_ant_sw_gpiochip, sx126x_ant_sw_line}}; - for (auto &pinMap : pinMappings) { - auto setMapIter = settingsMap.find(pinMap.pin); - if (setMapIter != settingsMap.end() && setMapIter->second != RADIOLIB_NC) { - if (initGPIOPin(setMapIter->second, gpioChipName + std::to_string(settingsMap[pinMap.gpiochip]), - settingsMap[pinMap.line]) != ERRNO_OK) { - printf("Error setting pin number %d. It may not exist, or may already be in use.\n", - settingsMap[pinMap.line]); - exit(EXIT_FAILURE); - } - } - } - SPI.begin(settingsStrings[spidev].c_str()); + if (portduino_config.lora_spi_dev != "" && portduino_config.lora_spi_dev != "ch341") { + SPI.begin(portduino_config.lora_spi_dev.c_str()); } - if (settingsStrings[traceFilename] != "") { + if (portduino_config.traceFilename != "") { try { - traceFile.open(settingsStrings[traceFilename], std::ios::out | std::ios::app); + traceFile.open(portduino_config.traceFilename, std::ios::out | std::ios::app); } catch (std::ofstream::failure &e) { std::cout << "*** traceFile Exception " << e.what() << std::endl; exit(EXIT_FAILURE); } } - if (verboseEnabled && settingsMap[logoutputlevel] != level_trace) { - settingsMap[logoutputlevel] = level_debug; + if (verboseEnabled && portduino_config.logoutputlevel != level_trace) { + portduino_config.logoutputlevel = level_debug; } return; @@ -537,99 +448,78 @@ bool loadConfig(const char *configPath) yamlConfig = YAML::LoadFile(configPath); if (yamlConfig["Logging"]) { if (yamlConfig["Logging"]["LogLevel"].as("info") == "trace") { - settingsMap[logoutputlevel] = level_trace; + portduino_config.logoutputlevel = level_trace; } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "debug") { - settingsMap[logoutputlevel] = level_debug; + portduino_config.logoutputlevel = level_debug; } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "info") { - settingsMap[logoutputlevel] = level_info; + portduino_config.logoutputlevel = level_info; } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "warn") { - settingsMap[logoutputlevel] = level_warn; + portduino_config.logoutputlevel = level_warn; } else if (yamlConfig["Logging"]["LogLevel"].as("info") == "error") { - settingsMap[logoutputlevel] = level_error; + portduino_config.logoutputlevel = level_error; } - settingsStrings[traceFilename] = yamlConfig["Logging"]["TraceFile"].as(""); + portduino_config.traceFilename = yamlConfig["Logging"]["TraceFile"].as(""); if (yamlConfig["Logging"]["AsciiLogs"]) { // Default is !isatty(1) but can be set explicitly in config.yaml - settingsMap[ascii_logs] = yamlConfig["Logging"]["AsciiLogs"].as(); + portduino_config.ascii_logs = yamlConfig["Logging"]["AsciiLogs"].as(); + portduino_config.ascii_logs_explicit = true; } } if (yamlConfig["Lora"]) { - const struct { - configNames cfgName; - std::string strName; - } loraModules[] = {{use_simradio, "sim"}, {use_autoconf, "auto"}, {use_rf95, "RF95"}, {use_sx1262, "sx1262"}, - {use_sx1268, "sx1268"}, {use_sx1280, "sx1280"}, {use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, - {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}}; - for (auto &loraModule : loraModules) { - settingsMap[loraModule.cfgName] = false; - } + if (yamlConfig["Lora"]["Module"]) { - for (auto &loraModule : loraModules) { - if (yamlConfig["Lora"]["Module"].as("") == loraModule.strName) { - settingsMap[loraModule.cfgName] = true; + for (auto &loraModule : portduino_config.loraModules) { + if (yamlConfig["Lora"]["Module"].as("") == loraModule.second) { + portduino_config.lora_module = loraModule.first; break; } } } + if (yamlConfig["Lora"]["SX126X_MAX_POWER"]) + portduino_config.sx126x_max_power = yamlConfig["Lora"]["SX126X_MAX_POWER"].as(22); + if (yamlConfig["Lora"]["SX128X_MAX_POWER"]) + portduino_config.sx128x_max_power = yamlConfig["Lora"]["SX128X_MAX_POWER"].as(13); + if (yamlConfig["Lora"]["LR1110_MAX_POWER"]) + portduino_config.lr1110_max_power = yamlConfig["Lora"]["LR1110_MAX_POWER"].as(22); + if (yamlConfig["Lora"]["LR1120_MAX_POWER"]) + portduino_config.lr1120_max_power = yamlConfig["Lora"]["LR1120_MAX_POWER"].as(13); + if (yamlConfig["Lora"]["RF95_MAX_POWER"]) + portduino_config.rf95_max_power = yamlConfig["Lora"]["RF95_MAX_POWER"].as(20); - settingsMap[sx126x_max_power] = yamlConfig["Lora"]["SX126X_MAX_POWER"].as(22); - settingsMap[sx128x_max_power] = yamlConfig["Lora"]["SX128X_MAX_POWER"].as(13); - settingsMap[lr1110_max_power] = yamlConfig["Lora"]["LR1110_MAX_POWER"].as(22); - settingsMap[lr1120_max_power] = yamlConfig["Lora"]["LR1120_MAX_POWER"].as(13); - settingsMap[rf95_max_power] = yamlConfig["Lora"]["RF95_MAX_POWER"].as(20); + if (portduino_config.lora_module != use_autoconf && portduino_config.lora_module != use_simradio && + !portduino_config.force_simradio) { + portduino_config.dio2_as_rf_switch = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as(false); + portduino_config.dio3_tcxo_voltage = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as(0) * 1000; + if (portduino_config.dio3_tcxo_voltage == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as(false)) { + portduino_config.dio3_tcxo_voltage = 1800; // default millivolts for "true" + } - settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as(false); - settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as(0) * 1000; - if (settingsMap[dio3_tcxo_voltage] == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as(false)) { - settingsMap[dio3_tcxo_voltage] = 1800; // default millivolts for "true" - } - - // backwards API compatibility and to globally set gpiochip once - int defaultGpioChip = settingsMap[default_gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0); - - const struct { - configNames pin; - configNames gpiochip; - configNames line; - std::string strName; - } pinMappings[] = { - {cs_pin, cs_gpiochip, cs_line, "CS"}, - {irq_pin, irq_gpiochip, irq_line, "IRQ"}, - {busy_pin, busy_gpiochip, busy_line, "Busy"}, - {reset_pin, reset_gpiochip, reset_line, "Reset"}, - {txen_pin, txen_gpiochip, txen_line, "TXen"}, - {rxen_pin, rxen_gpiochip, rxen_line, "RXen"}, - {sx126x_ant_sw_pin, sx126x_ant_sw_gpiochip, sx126x_ant_sw_line, "SX126X_ANT_SW"}, - }; - for (auto &pinMap : pinMappings) { - if (yamlConfig["Lora"][pinMap.strName].IsMap()) { - settingsMap[pinMap.pin] = yamlConfig["Lora"][pinMap.strName]["pin"].as(RADIOLIB_NC); - settingsMap[pinMap.line] = yamlConfig["Lora"][pinMap.strName]["line"].as(settingsMap[pinMap.pin]); - settingsMap[pinMap.gpiochip] = yamlConfig["Lora"][pinMap.strName]["gpiochip"].as(defaultGpioChip); - } else { // backwards API compatibility - settingsMap[pinMap.pin] = yamlConfig["Lora"][pinMap.strName].as(RADIOLIB_NC); - settingsMap[pinMap.line] = settingsMap[pinMap.pin]; - settingsMap[pinMap.gpiochip] = defaultGpioChip; + // backwards API compatibility and to globally set gpiochip once + portduino_config.lora_default_gpiochip = yamlConfig["Lora"]["gpiochip"].as(0); + for (auto this_pin : portduino_config.all_pins) { + if (this_pin->config_section == "Lora") { + readGPIOFromYaml(yamlConfig["Lora"][this_pin->config_name], *this_pin); + } } } - settingsMap[spiSpeed] = yamlConfig["Lora"]["spiSpeed"].as(2000000); - settingsStrings[lora_usb_serial_num] = yamlConfig["Lora"]["USB_Serialnum"].as(""); - settingsMap[lora_usb_pid] = yamlConfig["Lora"]["USB_PID"].as(0x5512); - settingsMap[lora_usb_vid] = yamlConfig["Lora"]["USB_VID"].as(0x1A86); + portduino_config.spiSpeed = yamlConfig["Lora"]["spiSpeed"].as(2000000); + portduino_config.lora_usb_serial_num = yamlConfig["Lora"]["USB_Serialnum"].as(""); + portduino_config.lora_usb_pid = yamlConfig["Lora"]["USB_PID"].as(0x5512); + portduino_config.lora_usb_vid = yamlConfig["Lora"]["USB_VID"].as(0x1A86); - settingsStrings[spidev] = yamlConfig["Lora"]["spidev"].as("spidev0.0"); - if (settingsStrings[spidev] != "ch341") { - settingsStrings[spidev] = "/dev/" + settingsStrings[spidev]; - if (settingsStrings[spidev].length() == 14) { - int x = settingsStrings[spidev].at(11) - '0'; - int y = settingsStrings[spidev].at(13) - '0'; + portduino_config.lora_spi_dev = yamlConfig["Lora"]["spidev"].as("spidev0.0"); + if (portduino_config.lora_spi_dev != "ch341") { + portduino_config.lora_spi_dev = "/dev/" + portduino_config.lora_spi_dev; + if (portduino_config.lora_spi_dev.length() == 14) { + int x = portduino_config.lora_spi_dev.at(11) - '0'; + int y = portduino_config.lora_spi_dev.at(13) - '0'; // Pretty sure this is always true if (x >= 0 && x < 10 && y >= 0 && y < 10) { // I believe this bit of weirdness is specifically for the new GUI - settingsMap[spidev] = x + y << 4; - settingsMap[displayspidev] = settingsMap[spidev]; - settingsMap[touchscreenspidev] = settingsMap[spidev]; + portduino_config.lora_spi_dev_int = x + y << 4; + portduino_config.display_spi_dev_int = portduino_config.lora_spi_dev_int; + portduino_config.touchscreen_spi_dev_int = portduino_config.lora_spi_dev_int; } } } @@ -676,163 +566,152 @@ bool loadConfig(const char *configPath) } } } - if (yamlConfig["GPIO"]) { - settingsMap[userButtonPin] = yamlConfig["GPIO"]["User"].as(RADIOLIB_NC); - } + readGPIOFromYaml(yamlConfig["GPIO"]["User"], portduino_config.userButtonPin); if (yamlConfig["GPS"]) { std::string serialPath = yamlConfig["GPS"]["SerialPath"].as(""); if (serialPath != "") { Serial1.setPath(serialPath); - settingsMap[has_gps] = 1; + portduino_config.has_gps = 1; } } if (yamlConfig["I2C"]) { - settingsStrings[i2cdev] = yamlConfig["I2C"]["I2CDevice"].as(""); + portduino_config.i2cdev = yamlConfig["I2C"]["I2CDevice"].as(""); } if (yamlConfig["Display"]) { - if (yamlConfig["Display"]["Panel"].as("") == "ST7789") - settingsMap[displayPanel] = st7789; - else if (yamlConfig["Display"]["Panel"].as("") == "ST7735") - settingsMap[displayPanel] = st7735; - else if (yamlConfig["Display"]["Panel"].as("") == "ST7735S") - settingsMap[displayPanel] = st7735s; - else if (yamlConfig["Display"]["Panel"].as("") == "ST7796") - settingsMap[displayPanel] = st7796; - else if (yamlConfig["Display"]["Panel"].as("") == "ILI9341") - settingsMap[displayPanel] = ili9341; - else if (yamlConfig["Display"]["Panel"].as("") == "ILI9342") - settingsMap[displayPanel] = ili9342; - else if (yamlConfig["Display"]["Panel"].as("") == "ILI9486") - settingsMap[displayPanel] = ili9486; - else if (yamlConfig["Display"]["Panel"].as("") == "ILI9488") - settingsMap[displayPanel] = ili9488; - else if (yamlConfig["Display"]["Panel"].as("") == "HX8357D") - settingsMap[displayPanel] = hx8357d; - else if (yamlConfig["Display"]["Panel"].as("") == "X11") - settingsMap[displayPanel] = x11; - else if (yamlConfig["Display"]["Panel"].as("") == "FB") - settingsMap[displayPanel] = fb; - settingsMap[displayHeight] = yamlConfig["Display"]["Height"].as(0); - settingsMap[displayWidth] = yamlConfig["Display"]["Width"].as(0); - settingsMap[displayDC] = yamlConfig["Display"]["DC"].as(-1); - settingsMap[displayCS] = yamlConfig["Display"]["CS"].as(-1); - settingsMap[displayRGBOrder] = yamlConfig["Display"]["RGBOrder"].as(false); - settingsMap[displayBacklight] = yamlConfig["Display"]["Backlight"].as(-1); - settingsMap[displayBacklightInvert] = yamlConfig["Display"]["BacklightInvert"].as(false); - settingsMap[displayBacklightPWMChannel] = yamlConfig["Display"]["BacklightPWMChannel"].as(-1); - settingsMap[displayReset] = yamlConfig["Display"]["Reset"].as(-1); - settingsMap[displayOffsetX] = yamlConfig["Display"]["OffsetX"].as(0); - settingsMap[displayOffsetY] = yamlConfig["Display"]["OffsetY"].as(0); - settingsMap[displayRotate] = yamlConfig["Display"]["Rotate"].as(false); - settingsMap[displayOffsetRotate] = yamlConfig["Display"]["OffsetRotate"].as(1); - settingsMap[displayInvert] = yamlConfig["Display"]["Invert"].as(false); - settingsMap[displayBusFrequency] = yamlConfig["Display"]["BusFrequency"].as(40000000); + + for (auto &screen_name : portduino_config.screen_names) { + if (yamlConfig["Display"]["Panel"].as("") == screen_name.second) + portduino_config.displayPanel = screen_name.first; + } + portduino_config.displayHeight = yamlConfig["Display"]["Height"].as(0); + portduino_config.displayWidth = yamlConfig["Display"]["Width"].as(0); + + readGPIOFromYaml(yamlConfig["Display"]["DC"], portduino_config.displayDC, -1); + readGPIOFromYaml(yamlConfig["Display"]["CS"], portduino_config.displayCS, -1); + readGPIOFromYaml(yamlConfig["Display"]["Backlight"], portduino_config.displayBacklight, -1); + readGPIOFromYaml(yamlConfig["Display"]["BacklightPWMChannel"], portduino_config.displayBacklightPWMChannel, -1); + readGPIOFromYaml(yamlConfig["Display"]["Reset"], portduino_config.displayReset, -1); + + portduino_config.displayBacklightInvert = yamlConfig["Display"]["BacklightInvert"].as(false); + portduino_config.displayRGBOrder = yamlConfig["Display"]["RGBOrder"].as(false); + portduino_config.displayOffsetX = yamlConfig["Display"]["OffsetX"].as(0); + portduino_config.displayOffsetY = yamlConfig["Display"]["OffsetY"].as(0); + portduino_config.displayRotate = yamlConfig["Display"]["Rotate"].as(false); + portduino_config.displayOffsetRotate = yamlConfig["Display"]["OffsetRotate"].as(1); + portduino_config.displayInvert = yamlConfig["Display"]["Invert"].as(false); + portduino_config.displayBusFrequency = yamlConfig["Display"]["BusFrequency"].as(40000000); if (yamlConfig["Display"]["spidev"]) { - settingsStrings[displayspidev] = "/dev/" + yamlConfig["Display"]["spidev"].as("spidev0.1"); - if (settingsStrings[displayspidev].length() == 14) { - int x = settingsStrings[displayspidev].at(11) - '0'; - int y = settingsStrings[displayspidev].at(13) - '0'; + portduino_config.display_spi_dev = "/dev/" + yamlConfig["Display"]["spidev"].as("spidev0.1"); + if (portduino_config.display_spi_dev.length() == 14) { + int x = portduino_config.display_spi_dev.at(11) - '0'; + int y = portduino_config.display_spi_dev.at(13) - '0'; if (x >= 0 && x < 10 && y >= 0 && y < 10) { - settingsMap[displayspidev] = x + y << 4; - settingsMap[touchscreenspidev] = settingsMap[displayspidev]; + portduino_config.display_spi_dev_int = x + y << 4; + portduino_config.touchscreen_spi_dev_int = portduino_config.display_spi_dev_int; } } } } if (yamlConfig["Touchscreen"]) { if (yamlConfig["Touchscreen"]["Module"].as("") == "XPT2046") - settingsMap[touchscreenModule] = xpt2046; + portduino_config.touchscreenModule = xpt2046; else if (yamlConfig["Touchscreen"]["Module"].as("") == "STMPE610") - settingsMap[touchscreenModule] = stmpe610; + portduino_config.touchscreenModule = stmpe610; else if (yamlConfig["Touchscreen"]["Module"].as("") == "GT911") - settingsMap[touchscreenModule] = gt911; + portduino_config.touchscreenModule = gt911; else if (yamlConfig["Touchscreen"]["Module"].as("") == "FT5x06") - settingsMap[touchscreenModule] = ft5x06; - settingsMap[touchscreenCS] = yamlConfig["Touchscreen"]["CS"].as(-1); - settingsMap[touchscreenIRQ] = yamlConfig["Touchscreen"]["IRQ"].as(-1); - settingsMap[touchscreenBusFrequency] = yamlConfig["Touchscreen"]["BusFrequency"].as(1000000); - settingsMap[touchscreenRotate] = yamlConfig["Touchscreen"]["Rotate"].as(-1); - settingsMap[touchscreenI2CAddr] = yamlConfig["Touchscreen"]["I2CAddr"].as(-1); + portduino_config.touchscreenModule = ft5x06; + + readGPIOFromYaml(yamlConfig["Touchscreen"]["CS"], portduino_config.touchscreenCS, -1); + readGPIOFromYaml(yamlConfig["Touchscreen"]["IRQ"], portduino_config.touchscreenIRQ, -1); + + portduino_config.touchscreenBusFrequency = yamlConfig["Touchscreen"]["BusFrequency"].as(1000000); + portduino_config.touchscreenRotate = yamlConfig["Touchscreen"]["Rotate"].as(-1); + portduino_config.touchscreenI2CAddr = yamlConfig["Touchscreen"]["I2CAddr"].as(-1); if (yamlConfig["Touchscreen"]["spidev"]) { - settingsStrings[touchscreenspidev] = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as(""); - if (settingsStrings[touchscreenspidev].length() == 14) { - int x = settingsStrings[touchscreenspidev].at(11) - '0'; - int y = settingsStrings[touchscreenspidev].at(13) - '0'; + portduino_config.touchscreen_spi_dev = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as(""); + if (portduino_config.touchscreen_spi_dev.length() == 14) { + int x = portduino_config.touchscreen_spi_dev.at(11) - '0'; + int y = portduino_config.touchscreen_spi_dev.at(13) - '0'; if (x >= 0 && x < 10 && y >= 0 && y < 10) { - settingsMap[touchscreenspidev] = x + y << 4; + portduino_config.touchscreen_spi_dev_int = x + y << 4; } } } } if (yamlConfig["Input"]) { - settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as(""); - settingsStrings[pointerDevice] = (yamlConfig["Input"]["PointerDevice"]).as(""); - settingsMap[userButtonPin] = yamlConfig["Input"]["User"].as(RADIOLIB_NC); - settingsMap[tbUpPin] = yamlConfig["Input"]["TrackballUp"].as(RADIOLIB_NC); - settingsMap[tbDownPin] = yamlConfig["Input"]["TrackballDown"].as(RADIOLIB_NC); - settingsMap[tbLeftPin] = yamlConfig["Input"]["TrackballLeft"].as(RADIOLIB_NC); - settingsMap[tbRightPin] = yamlConfig["Input"]["TrackballRight"].as(RADIOLIB_NC); - settingsMap[tbPressPin] = yamlConfig["Input"]["TrackballPress"].as(RADIOLIB_NC); + portduino_config.keyboardDevice = (yamlConfig["Input"]["KeyboardDevice"]).as(""); + portduino_config.pointerDevice = (yamlConfig["Input"]["PointerDevice"]).as(""); + + readGPIOFromYaml(yamlConfig["Input"]["User"], portduino_config.userButtonPin); + readGPIOFromYaml(yamlConfig["Input"]["TrackballUp"], portduino_config.tbUpPin); + readGPIOFromYaml(yamlConfig["Input"]["TrackballDown"], portduino_config.tbDownPin); + readGPIOFromYaml(yamlConfig["Input"]["TrackballLeft"], portduino_config.tbLeftPin); + readGPIOFromYaml(yamlConfig["Input"]["TrackballRight"], portduino_config.tbRightPin); + readGPIOFromYaml(yamlConfig["Input"]["TrackballPress"], portduino_config.tbPressPin); + if (yamlConfig["Input"]["TrackballDirection"].as("RISING") == "RISING") { - settingsMap[tbDirection] = 4; + portduino_config.tbDirection = 4; } else if (yamlConfig["Input"]["TrackballDirection"].as("RISING") == "FALLING") { - settingsMap[tbDirection] = 3; + portduino_config.tbDirection = 3; } } if (yamlConfig["Webserver"]) { - settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as(-1); - settingsStrings[webserverrootpath] = + portduino_config.webserverport = (yamlConfig["Webserver"]["Port"]).as(-1); + portduino_config.webserver_root_path = (yamlConfig["Webserver"]["RootPath"]).as("/usr/share/meshtasticd/web"); - settingsStrings[websslkeypath] = + portduino_config.webserver_ssl_key_path = (yamlConfig["Webserver"]["SSLKey"]).as("/etc/meshtasticd/ssl/private_key.pem"); - settingsStrings[websslcertpath] = + portduino_config.webserver_ssl_cert_path = (yamlConfig["Webserver"]["SSLCert"]).as("/etc/meshtasticd/ssl/certificate.pem"); } if (yamlConfig["HostMetrics"]) { - settingsMap[hostMetrics_channel] = (yamlConfig["HostMetrics"]["Channel"]).as(0); - settingsMap[hostMetrics_interval] = (yamlConfig["HostMetrics"]["ReportInterval"]).as(0); - settingsStrings[hostMetrics_user_command] = (yamlConfig["HostMetrics"]["UserStringCommand"]).as(""); + portduino_config.hostMetrics_channel = (yamlConfig["HostMetrics"]["Channel"]).as(0); + portduino_config.hostMetrics_interval = (yamlConfig["HostMetrics"]["ReportInterval"]).as(0); + portduino_config.hostMetrics_user_command = (yamlConfig["HostMetrics"]["UserStringCommand"]).as(""); } if (yamlConfig["Config"]) { if (yamlConfig["Config"]["DisplayMode"]) { - settingsMap[has_configDisplayMode] = true; + portduino_config.has_configDisplayMode = true; if ((yamlConfig["Config"]["DisplayMode"]).as("") == "TWOCOLOR") { - settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR; + portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR; } else if ((yamlConfig["Config"]["DisplayMode"]).as("") == "INVERTED") { - settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED; + portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED; } else if ((yamlConfig["Config"]["DisplayMode"]).as("") == "COLOR") { - settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_COLOR; + portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR; } else { - settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; + portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; } } } if (yamlConfig["General"]) { - settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as(200); - settingsMap[maxtophone] = (yamlConfig["General"]["MaxMessageQueue"]).as(100); - settingsStrings[config_directory] = (yamlConfig["General"]["ConfigDirectory"]).as(""); - settingsStrings[available_directory] = + portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as(200); + portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as(100); + portduino_config.config_directory = (yamlConfig["General"]["ConfigDirectory"]).as(""); + portduino_config.available_directory = (yamlConfig["General"]["AvailableDirectory"]).as("/etc/meshtasticd/available.d/"); if ((yamlConfig["General"]["MACAddress"]).as("") != "" && (yamlConfig["General"]["MACAddressSource"]).as("") != "") { std::cout << "Cannot set both MACAddress and MACAddressSource!" << std::endl; exit(EXIT_FAILURE); } - settingsStrings[mac_address] = (yamlConfig["General"]["MACAddress"]).as(""); - if ((yamlConfig["General"]["MACAddressSource"]).as("") != "") { - std::ifstream infile("/sys/class/net/" + (yamlConfig["General"]["MACAddressSource"]).as("") + - "/address"); - std::getline(infile, settingsStrings[mac_address]); + portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as(""); + if (portduino_config.mac_address != "") { + portduino_config.mac_address_explicit = true; + } else if ((yamlConfig["General"]["MACAddressSource"]).as("") != "") { + portduino_config.mac_address_source = (yamlConfig["General"]["MACAddressSource"]).as(""); + std::ifstream infile("/sys/class/net/" + portduino_config.mac_address_source + "/address"); + std::getline(infile, portduino_config.mac_address); } // https://stackoverflow.com/a/20326454 - settingsStrings[mac_address].erase( - std::remove(settingsStrings[mac_address].begin(), settingsStrings[mac_address].end(), ':'), - settingsStrings[mac_address].end()); + portduino_config.mac_address.erase( + std::remove(portduino_config.mac_address.begin(), portduino_config.mac_address.end(), ':'), + portduino_config.mac_address.end()); } } catch (YAML::Exception &e) { std::cout << "*** Exception " << e.what() << std::endl; @@ -851,12 +730,12 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac) { mac_str.erase(std::remove(mac_str.begin(), mac_str.end(), ':'), mac_str.end()); if (mac_str.length() == 12) { - dmac[0] = std::stoi(settingsStrings[mac_address].substr(0, 2), nullptr, 16); - dmac[1] = std::stoi(settingsStrings[mac_address].substr(2, 2), nullptr, 16); - dmac[2] = std::stoi(settingsStrings[mac_address].substr(4, 2), nullptr, 16); - dmac[3] = std::stoi(settingsStrings[mac_address].substr(6, 2), nullptr, 16); - dmac[4] = std::stoi(settingsStrings[mac_address].substr(8, 2), nullptr, 16); - dmac[5] = std::stoi(settingsStrings[mac_address].substr(10, 2), nullptr, 16); + dmac[0] = std::stoi(portduino_config.mac_address.substr(0, 2), nullptr, 16); + dmac[1] = std::stoi(portduino_config.mac_address.substr(2, 2), nullptr, 16); + dmac[2] = std::stoi(portduino_config.mac_address.substr(4, 2), nullptr, 16); + dmac[3] = std::stoi(portduino_config.mac_address.substr(6, 2), nullptr, 16); + dmac[4] = std::stoi(portduino_config.mac_address.substr(8, 2), nullptr, 16); + dmac[5] = std::stoi(portduino_config.mac_address.substr(10, 2), nullptr, 16); return true; } else { return false; @@ -875,4 +754,19 @@ std::string exec(const char *cmd) result += buffer.data(); } return result; +} + +void readGPIOFromYaml(YAML::Node sourceNode, pinMapping &destPin, int pinDefault) +{ + if (sourceNode.IsMap()) { + destPin.enabled = true; + destPin.pin = sourceNode["pin"].as(pinDefault); + destPin.line = sourceNode["line"].as(destPin.pin); + destPin.gpiochip = sourceNode["gpiochip"].as(portduino_config.lora_default_gpiochip); + } else if (sourceNode) { // backwards API compatibility + destPin.enabled = true; + destPin.pin = sourceNode.as(pinDefault); + destPin.line = destPin.pin; + destPin.gpiochip = portduino_config.lora_default_gpiochip; + } } \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 8c36a1180..106900c48 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -6,6 +6,7 @@ #include "LR11x0Interface.h" #include "Module.h" #include "platform/portduino/USBHal.h" +#include "yaml-cpp/yaml.h" // Product strings for auto-configuration // {"PRODUCT_STRING", "CONFIG.YAML"} @@ -19,36 +20,10 @@ inline const std::unordered_map configProducts = { {"RAK6421-13300-S1", "lora-RAK6421-13300-slot1.yaml"}, {"RAK6421-13300-S2", "lora-RAK6421-13300-slot2.yaml"}}; -enum configNames { - default_gpiochip, - cs_pin, - cs_line, - cs_gpiochip, - irq_pin, - irq_line, - irq_gpiochip, - busy_pin, - busy_line, - busy_gpiochip, - reset_pin, - reset_line, - reset_gpiochip, - txen_pin, - txen_line, - txen_gpiochip, - rxen_pin, - rxen_line, - rxen_gpiochip, - sx126x_ant_sw_pin, - sx126x_ant_sw_line, - sx126x_ant_sw_gpiochip, - sx126x_max_power, - sx128x_max_power, - lr1110_max_power, - lr1120_max_power, - rf95_max_power, - dio2_as_rf_switch, - dio3_tcxo_voltage, +enum screen_modules { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d }; +enum touchscreen_modules { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; +enum portduino_log_level { level_error, level_warn, level_info, level_debug, level_trace }; +enum lora_module_enum { use_simradio, use_autoconf, use_rf95, @@ -58,72 +33,18 @@ enum configNames { use_lr1110, use_lr1120, use_lr1121, - use_llcc68, - lora_usb_serial_num, - lora_usb_pid, - lora_usb_vid, - userButtonPin, - tbUpPin, - tbDownPin, - tbLeftPin, - tbRightPin, - tbPressPin, - tbDirection, - spidev, - spiSpeed, - i2cdev, - has_gps, - touchscreenModule, - touchscreenCS, - touchscreenIRQ, - touchscreenI2CAddr, - touchscreenBusFrequency, - touchscreenRotate, - touchscreenspidev, - displayspidev, - displayBusFrequency, - displayPanel, - displayWidth, - displayHeight, - displayCS, - displayDC, - displayRGBOrder, - displayBacklight, - displayBacklightPWMChannel, - displayBacklightInvert, - displayReset, - displayRotate, - displayOffsetRotate, - displayOffsetX, - displayOffsetY, - displayInvert, - keyboardDevice, - pointerDevice, - logoutputlevel, - traceFilename, - webserver, - webserverport, - webserverrootpath, - websslkeypath, - websslcertpath, - maxtophone, - maxnodes, - ascii_logs, - config_directory, - available_directory, - mac_address, - hostMetrics_interval, - hostMetrics_channel, - hostMetrics_user_command, - configDisplayMode, - has_configDisplayMode + use_llcc68 +}; + +struct pinMapping { + std::string config_section; + std::string config_name; + int pin = RADIOLIB_NC; + int gpiochip; + int line; + bool enabled = false; }; -enum { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d }; -enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 }; -enum { level_error, level_warn, level_info, level_debug, level_trace }; -extern std::map settingsMap; -extern std::map settingsStrings; extern std::ofstream traceFile; extern Ch341Hal *ch341Hal; int initGPIOPin(int pinNum, std::string gpioChipname, int line); @@ -131,13 +52,422 @@ bool loadConfig(const char *configPath); static bool ends_with(std::string_view str, std::string_view suffix); void getMacAddr(uint8_t *dmac); bool MAC_from_string(std::string mac_str, uint8_t *dmac); +void readGPIOFromYaml(YAML::Node sourceNode, pinMapping &destPin, int pinDefault = RADIOLIB_NC); std::string exec(const char *cmd); extern struct portduino_config_struct { + // Lora + std::map loraModules = { + {use_simradio, "sim"}, {use_autoconf, "auto"}, {use_rf95, "RF95"}, {use_sx1262, "sx1262"}, {use_sx1268, "sx1268"}, + {use_sx1280, "sx1280"}, {use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}}; + + std::map screen_names = {{x11, "X11"}, {fb, "FB"}, {st7789, "ST7789"}, + {st7735, "ST7735"}, {st7735s, "ST7735S"}, {st7796, "ST7796"}, + {ili9341, "ILI9341"}, {ili9342, "ILI9342"}, {ili9486, "ILI9486"}, + {ili9488, "ILI9488"}, {hx8357d, "HX8357D"}}; + + lora_module_enum lora_module; bool has_rfswitch_table = false; uint32_t rfswitch_dio_pins[5] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; Module::RfSwitchMode_t rfswitch_table[8]; bool force_simradio = false; bool has_device_id = false; uint8_t device_id[16] = {0}; + std::string lora_spi_dev = ""; + std::string lora_usb_serial_num = ""; + int lora_spi_dev_int = 0; + int lora_default_gpiochip = 0; + int sx126x_max_power = 22; + int sx128x_max_power = 13; + int lr1110_max_power = 22; + int lr1120_max_power = 13; + int rf95_max_power = 20; + bool dio2_as_rf_switch = false; + int dio3_tcxo_voltage = 0; + int lora_usb_pid = 0x5512; + int lora_usb_vid = 0x1A86; + int spiSpeed = 2000000; + pinMapping lora_cs_pin = {"Lora", "CS"}; + pinMapping lora_irq_pin = {"Lora", "IRQ"}; + pinMapping lora_busy_pin = {"Lora", "Busy"}; + pinMapping lora_reset_pin = {"Lora", "Reset"}; + pinMapping lora_txen_pin = {"Lora", "TXen"}; + pinMapping lora_rxen_pin = {"Lora", "RXen"}; + pinMapping lora_sx126x_ant_sw_pin = {"Lora", "SX126X_ANT_SW"}; + + // GPS + bool has_gps = false; + + // I2C + std::string i2cdev = ""; + + // Display + std::string display_spi_dev = ""; + int display_spi_dev_int = 0; + int displayBusFrequency = 40000000; + screen_modules displayPanel = no_screen; + int displayWidth = 0; + int displayHeight = 0; + bool displayRGBOrder = false; + bool displayBacklightInvert = false; + bool displayRotate = false; + int displayOffsetRotate = 1; + bool displayInvert = false; + int displayOffsetX = 0; + int displayOffsetY = 0; + pinMapping displayDC = {"Display", "DC"}; + pinMapping displayCS = {"Display", "CS"}; + pinMapping displayBacklight = {"Display", "Backlight"}; + pinMapping displayBacklightPWMChannel = {"Display", "BacklightPWMChannel"}; + pinMapping displayReset = {"Display", "Reset"}; + + // Touchscreen + std::string touchscreen_spi_dev = ""; + int touchscreen_spi_dev_int = 0; + touchscreen_modules touchscreenModule = no_touchscreen; + int touchscreenI2CAddr = -1; + int touchscreenBusFrequency = 1000000; + int touchscreenRotate = -1; + pinMapping touchscreenCS = {"Touchscreen", "CS"}; + pinMapping touchscreenIRQ = {"Touchscreen", "IRQ"}; + + // Input + std::string keyboardDevice = ""; + std::string pointerDevice = ""; + int tbDirection; + pinMapping userButtonPin = {"Input", "User"}; + pinMapping tbUpPin = {"Input", "TrackballUp"}; + pinMapping tbDownPin = {"Input", "TrackballDown"}; + pinMapping tbLeftPin = {"Input", "TrackballLwft"}; + pinMapping tbRightPin = {"Input", "TrackballRight"}; + pinMapping tbPressPin = {"Input", "TrackballPress"}; + + // Logging + portduino_log_level logoutputlevel = level_debug; + std::string traceFilename; + bool ascii_logs = !isatty(1); + bool ascii_logs_explicit = false; + + // Webserver + std::string webserver_root_path = ""; + std::string webserver_ssl_key_path = "/etc/meshtasticd/ssl/private_key.pem"; + std::string webserver_ssl_cert_path = "/etc/meshtasticd/ssl/certificate.pem"; + int webserverport = -1; + + // HostMetrics + std::string hostMetrics_user_command = ""; + int hostMetrics_interval = 0; + int hostMetrics_channel = 0; + + // config + int configDisplayMode = 0; + bool has_configDisplayMode = false; + + // General + std::string mac_address = ""; + bool mac_address_explicit = false; + std::string mac_address_source = ""; + std::string config_directory = ""; + std::string available_directory = "/etc/meshtasticd/available.d/"; + int maxtophone = 100; + int MaxNodes = 200; + + pinMapping *all_pins[20] = {&lora_cs_pin, + &lora_irq_pin, + &lora_busy_pin, + &lora_reset_pin, + &lora_txen_pin, + &lora_rxen_pin, + &lora_sx126x_ant_sw_pin, + &displayDC, + &displayCS, + &displayBacklight, + &displayBacklightPWMChannel, + &displayReset, + &touchscreenCS, + &touchscreenIRQ, + &userButtonPin, + &tbUpPin, + &tbDownPin, + &tbLeftPin, + &tbRightPin, + &tbPressPin}; + + std::string emit_yaml() + { + YAML::Emitter out; + out << YAML::BeginMap; + + // Lora + out << YAML::Key << "Lora" << YAML::Value << YAML::BeginMap; + out << YAML::Key << "Module" << YAML::Value << loraModules[lora_module]; + + for (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; + out << YAML::Key << "line" << YAML::Value << lora_pin->line; + out << YAML::Key << "gpiochip" << YAML::Value << lora_pin->gpiochip; + out << YAML::EndMap; // User + } + } + + if (sx126x_max_power != 22) + out << YAML::Key << "SX126X_MAX_POWER" << YAML::Value << sx126x_max_power; + if (sx128x_max_power != 13) + out << YAML::Key << "SX128X_MAX_POWER" << YAML::Value << sx128x_max_power; + if (lr1110_max_power != 22) + out << YAML::Key << "LR1110_MAX_POWER" << YAML::Value << lr1110_max_power; + if (lr1120_max_power != 13) + out << YAML::Key << "LR1120_MAX_POWER" << YAML::Value << lr1120_max_power; + if (rf95_max_power != 20) + out << YAML::Key << "RF95_MAX_POWER" << YAML::Value << rf95_max_power; + 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 << dio3_tcxo_voltage; + 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 != "") + 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 (rfswitch_dio_pins[0] != RADIOLIB_NC) { + out << YAML::Key << "rfswitch_table" << YAML::Value << YAML::BeginMap; + + out << YAML::Key << "pins"; + out << YAML::Value << YAML::Flow << YAML::BeginSeq; + + for (int i = 0; i < 5; i++) { + // set up the pin array first + if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO5) + out << "DIO5"; + if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO6) + out << "DIO6"; + if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO7) + out << "DIO7"; + if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO8) + out << "DIO8"; + if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO10) + out << "DIO10"; + } + out << YAML::EndSeq; + + for (int i = 0; i < 7; i++) { + switch (i) { + case 0: + out << YAML::Key << "MODE_STBY"; + break; + case 1: + out << YAML::Key << "MODE_RX"; + break; + case 2: + out << YAML::Key << "MODE_TX"; + break; + case 3: + out << YAML::Key << "MODE_TX_HP"; + break; + case 4: + out << YAML::Key << "MODE_TX_HF"; + break; + case 5: + out << YAML::Key << "MODE_GNSS"; + break; + case 6: + out << YAML::Key << "MODE_WIFI"; + break; + } + + out << YAML::Value << YAML::Flow << YAML::BeginSeq; + for (int j = 0; j < 5; j++) { + if (rfswitch_table[i].values[j] == HIGH) { + out << "HIGH"; + } else { + out << "LOW"; + } + } + out << YAML::EndSeq; + } + out << YAML::EndMap; // rfswitch_table + } + out << YAML::EndMap; // Lora + + if (i2cdev != "") { + out << YAML::Key << "I2C" << YAML::Value << YAML::BeginMap; + out << YAML::Key << "I2CDevice" << YAML::Value << i2cdev; + out << YAML::EndMap; // I2C + } + + // Display + if (displayPanel != no_screen) { + out << YAML::Key << "Display" << YAML::Value << YAML::BeginMap; + for (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) { + 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; + out << YAML::Key << "line" << YAML::Value << display_pin->line; + out << YAML::Key << "gpiochip" << YAML::Value << display_pin->gpiochip; + out << YAML::EndMap; + } + } + out << YAML::Key << "spidev" << YAML::Value << display_spi_dev; + out << YAML::Key << "BusFrequency" << YAML::Value << displayBusFrequency; + if (displayWidth) + out << YAML::Key << "Width" << YAML::Value << displayWidth; + if (displayHeight) + out << YAML::Key << "Height" << YAML::Value << displayHeight; + if (displayRGBOrder) + out << YAML::Key << "RGBOrder" << YAML::Value << true; + if (displayBacklightInvert) + out << YAML::Key << "BacklightInvert" << YAML::Value << true; + if (displayRotate) + out << YAML::Key << "Rotate" << YAML::Value << true; + if (displayInvert) + out << YAML::Key << "Invert" << YAML::Value << true; + if (displayOffsetX) + out << YAML::Key << "OffsetX" << YAML::Value << displayOffsetX; + if (displayOffsetY) + out << YAML::Key << "OffsetY" << YAML::Value << displayOffsetY; + + out << YAML::Key << "OffsetRotate" << YAML::Value << displayOffsetRotate; + + out << YAML::EndMap; // Display + } + + // Touchscreen + if (touchscreen_spi_dev != "") { + out << YAML::Key << "Touchscreen" << YAML::Value << YAML::BeginMap; + out << YAML::Key << "spidev" << YAML::Value << touchscreen_spi_dev; + out << YAML::Key << "BusFrequency" << YAML::Value << touchscreenBusFrequency; + switch (touchscreenModule) { + case xpt2046: + out << YAML::Key << "Module" << YAML::Value << "XPT2046"; + case stmpe610: + out << YAML::Key << "Module" << YAML::Value << "STMPE610"; + case gt911: + out << YAML::Key << "Module" << YAML::Value << "GT911"; + case ft5x06: + out << YAML::Key << "Module" << YAML::Value << "FT5x06"; + } + for (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; + out << YAML::Key << "line" << YAML::Value << touchscreen_pin->line; + out << YAML::Key << "gpiochip" << YAML::Value << touchscreen_pin->gpiochip; + out << YAML::EndMap; + } + } + if (touchscreenRotate != -1) + out << YAML::Key << "Rotate" << YAML::Value << touchscreenRotate; + if (touchscreenI2CAddr != -1) + out << YAML::Key << "I2CAddr" << YAML::Value << touchscreenI2CAddr; + out << YAML::EndMap; // Touchscreen + } + + // Input + out << YAML::Key << "Input" << YAML::Value << YAML::BeginMap; + if (keyboardDevice != "") + out << YAML::Key << "KeyboardDevice" << YAML::Value << keyboardDevice; + if (pointerDevice != "") + out << YAML::Key << "PointerDevice" << YAML::Value << pointerDevice; + + for (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; + out << YAML::Key << "line" << YAML::Value << input_pin->line; + out << YAML::Key << "gpiochip" << YAML::Value << input_pin->gpiochip; + out << YAML::EndMap; + } + } + if (tbDirection == 3) + out << YAML::Key << "TrackballDirection" << YAML::Value << "FALLING"; + + out << YAML::EndMap; // Input + + out << YAML::Key << "Logging" << YAML::Value << YAML::BeginMap; + out << YAML::Key << "LogLevel" << YAML::Value; + switch (logoutputlevel) { + case level_error: + out << "error"; + break; + case level_warn: + out << "warn"; + break; + case level_info: + out << "info"; + break; + case level_debug: + out << "debug"; + break; + case level_trace: + out << "trace"; + break; + } + if (traceFilename != "") + out << YAML::Key << "TraceFile" << YAML::Value << traceFilename; + if (ascii_logs_explicit) { + out << YAML::Key << "AsciiLogs" << YAML::Value << ascii_logs; + } + out << YAML::EndMap; // Logging + + // Webserver + if (webserver_root_path != "") { + out << YAML::Key << "Webserver" << YAML::Value << YAML::BeginMap; + out << YAML::Key << "RootPath" << YAML::Value << webserver_root_path; + out << YAML::Key << "SSLKey" << YAML::Value << webserver_ssl_key_path; + out << YAML::Key << "SSLCert" << YAML::Value << webserver_ssl_cert_path; + out << YAML::Key << "Port" << YAML::Value << webserverport; + out << YAML::EndMap; // Webserver + } + + // HostMetrics + if (hostMetrics_user_command != "") { + out << YAML::Key << "HostMetrics" << YAML::Value << YAML::BeginMap; + out << YAML::Key << "UserStringCommand" << YAML::Value << hostMetrics_user_command; + out << YAML::Key << "ReportInterval" << YAML::Value << hostMetrics_interval; + out << YAML::Key << "Channel" << YAML::Value << hostMetrics_channel; + + out << YAML::EndMap; // HostMetrics + } + + // config + if (has_configDisplayMode) { + out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap; + switch (configDisplayMode) { + case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; + case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: + out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; + case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: + out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; + case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: + out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; + } + + out << YAML::EndMap; // Config + } + + // General + out << YAML::Key << "General" << YAML::Value << YAML::BeginMap; + if (config_directory != "") + out << YAML::Key << "ConfigDirectory" << YAML::Value << config_directory; + if (mac_address_explicit) + out << YAML::Key << "MACAddress" << YAML::Value << mac_address; + if (mac_address_source != "") + out << YAML::Key << "MACAddressSource" << YAML::Value << mac_address_source; + if (available_directory != "") + out << YAML::Key << "AvailableDirectory" << YAML::Value << available_directory; + out << YAML::Key << "MaxMessageQueue" << YAML::Value << maxtophone; + out << YAML::Key << "MaxNodes" << YAML::Value << MaxNodes; + out << YAML::EndMap; // General + return out.c_str(); + } } portduino_config; \ No newline at end of file diff --git a/src/platform/portduino/architecture.h b/src/platform/portduino/architecture.h index 07d0aeee0..e10519d21 100644 --- a/src/platform/portduino/architecture.h +++ b/src/platform/portduino/architecture.h @@ -28,9 +28,9 @@ #endif #ifndef HAS_TRACKBALL #define HAS_TRACKBALL 1 -#define TB_DOWN (uint8_t) settingsMap[tbDownPin] -#define TB_UP (uint8_t) settingsMap[tbUpPin] -#define TB_LEFT (uint8_t) settingsMap[tbLeftPin] -#define TB_RIGHT (uint8_t) settingsMap[tbRightPin] -#define TB_PRESS (uint8_t) settingsMap[tbPressPin] +#define TB_DOWN (uint8_t) portduino_config.tbDownPin.pin +#define TB_UP (uint8_t) portduino_config.tbUpPin.pin +#define TB_LEFT (uint8_t) portduino_config.tbLeftPin.pin +#define TB_RIGHT (uint8_t) portduino_config.tbRightPin.pin +#define TB_PRESS (uint8_t) portduino_config.tbPressPin.pin #endif \ No newline at end of file diff --git a/variants/native/portduino-buildroot/variant.h b/variants/native/portduino-buildroot/variant.h index b7b39d6e8..a453c3b71 100644 --- a/variants/native/portduino-buildroot/variant.h +++ b/variants/native/portduino-buildroot/variant.h @@ -1,5 +1,5 @@ #define HAS_SCREEN 1 #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 -#define MAX_RX_TOPHONE settingsMap[maxtophone] -#define MAX_NUM_NODES settingsMap[maxnodes] \ No newline at end of file +#define MAX_RX_TOPHONE portduino_config.maxtophone +#define MAX_NUM_NODES portduino_config.MaxNodes \ No newline at end of file diff --git a/variants/native/portduino/variant.h b/variants/native/portduino/variant.h index a7ca865be..af05fcf8d 100644 --- a/variants/native/portduino/variant.h +++ b/variants/native/portduino/variant.h @@ -3,8 +3,8 @@ #endif #define CANNED_MESSAGE_MODULE_ENABLE 1 #define HAS_GPS 1 -#define MAX_RX_TOPHONE settingsMap[maxtophone] -#define MAX_NUM_NODES settingsMap[maxnodes] +#define MAX_RX_TOPHONE portduino_config.maxtophone +#define MAX_NUM_NODES portduino_config.MaxNodes // RAK12002 RTC Module -#define RV3028_RTC (uint8_t)0b1010010 \ No newline at end of file +#define RV3028_RTC (uint8_t)0b1010010 From 18550ea80c143a420b3033106f6cc6f852d3faa3 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Thu, 4 Sep 2025 14:17:21 +1000 Subject: [PATCH 26/81] chore(deps): update meshtastic/device-ui digest to 10f0244 (#7840) (#7847) 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 67c3f8a8c..b0f73bc09 100644 --- a/platformio.ini +++ b/platformio.ini @@ -118,7 +118,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/8019704395b7539600d581330499208edcd80804.zip + https://github.com/meshtastic/device-ui/archive/10f02441ec7dcd099c4c5165c709afc3e0e3cb88.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 2e8f4ad6af782141610dcc874f9e33d5df81ad7a Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Thu, 4 Sep 2025 06:12:47 +0100 Subject: [PATCH 27/81] Add RF switch settings for STM32WL variants (#7813) * Add RF switch settings for STM32WL variants * Shuffle ifdefs in STM32WLE5JCInterface to make it not get built by other targets --- src/mesh/STM32WLE5JCInterface.cpp | 6 +++--- src/mesh/STM32WLE5JCInterface.h | 13 ++----------- variants/stm32/CDEBYTE_E77-MBL/platformio.ini | 2 -- variants/stm32/CDEBYTE_E77-MBL/rfswitch.h | 9 +++++++++ variants/stm32/CDEBYTE_E77-MBL/variant.h | 1 - variants/stm32/rak3172/platformio.ini | 2 +- variants/stm32/rak3172/rfswitch.h | 7 +++++++ variants/stm32/wio-e5/rfswitch.h | 8 ++++++++ 8 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 variants/stm32/CDEBYTE_E77-MBL/rfswitch.h create mode 100644 variants/stm32/rak3172/rfswitch.h create mode 100644 variants/stm32/wio-e5/rfswitch.h diff --git a/src/mesh/STM32WLE5JCInterface.cpp b/src/mesh/STM32WLE5JCInterface.cpp index d7bc37466..f6e4b3512 100644 --- a/src/mesh/STM32WLE5JCInterface.cpp +++ b/src/mesh/STM32WLE5JCInterface.cpp @@ -1,13 +1,13 @@ -#include "STM32WLE5JCInterface.h" #include "configuration.h" + +#ifdef ARCH_STM32WL +#include "STM32WLE5JCInterface.h" #include "error.h" #ifndef STM32WLx_MAX_POWER #define STM32WLx_MAX_POWER 22 #endif -#ifdef ARCH_STM32WL - STM32WLE5JCInterface::STM32WLE5JCInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) : SX126xInterface(hal, cs, irq, rst, busy) diff --git a/src/mesh/STM32WLE5JCInterface.h b/src/mesh/STM32WLE5JCInterface.h index 0c8140290..ee935375e 100644 --- a/src/mesh/STM32WLE5JCInterface.h +++ b/src/mesh/STM32WLE5JCInterface.h @@ -1,8 +1,8 @@ #pragma once -#include "SX126xInterface.h" - #ifdef ARCH_STM32WL +#include "SX126xInterface.h" +#include "rfswitch.h" /** * Our adapter for STM32WLE5JC radios @@ -16,13 +16,4 @@ class STM32WLE5JCInterface : public SX126xInterface virtual bool init() override; }; -/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/ - * Wio-E5 module ONLY transmits through RFO_HP - * Receive: PA4=1, PA5=0 - * Transmit(high output power, SMPS mode): PA4=0, PA5=1 */ -static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA4, PA5, 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}; - #endif // ARCH_STM32WL \ No newline at end of file diff --git a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini index c011f62c9..290982405 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini +++ b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini @@ -12,7 +12,5 @@ build_flags = -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 -DMESHTASTIC_EXCLUDE_I2C=1 -DMESHTASTIC_EXCLUDE_GPS=1 - ;-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF - ;-DCFG_DEBUG upload_port = stlink \ No newline at end of file diff --git a/variants/stm32/CDEBYTE_E77-MBL/rfswitch.h b/variants/stm32/CDEBYTE_E77-MBL/rfswitch.h new file mode 100644 index 000000000..daf4aaaf9 --- /dev/null +++ b/variants/stm32/CDEBYTE_E77-MBL/rfswitch.h @@ -0,0 +1,9 @@ +// From E77-900M22S Product Specification +// https://www.cdebyte.com/pdf-down.aspx?id=2963 +// Note 1: PA6 and PA7 pins are used as internal control RF switches of the module, PA6 = RF_TXEN, PA7 = RF_RXEN, RF_TXEN=1 +// RF_RXEN=0 is the transmit channel, and RF_TXEN=0 RF_RXEN=1 is the receiving channel + +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA7, PA6, 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}; \ No newline at end of file diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index 52801dac7..317f44489 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -18,5 +18,4 @@ Do not expect a working Meshtastic device with this target. #define LED_PIN PB4 // LED1 // #define LED_PIN PB3 // LED2 #define LED_STATE_ON 1 - #endif diff --git a/variants/stm32/rak3172/platformio.ini b/variants/stm32/rak3172/platformio.ini index a12b9f21c..7fc6c7cba 100644 --- a/variants/stm32/rak3172/platformio.ini +++ b/variants/stm32/rak3172/platformio.ini @@ -15,5 +15,5 @@ build_flags = -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 -DMESHTASTIC_EXCLUDE_I2C=1 -DMESHTASTIC_EXCLUDE_GPS=1 - ;-DCFG_DEBUG + upload_port = stlink diff --git a/variants/stm32/rak3172/rfswitch.h b/variants/stm32/rak3172/rfswitch.h new file mode 100644 index 000000000..2dced3c7c --- /dev/null +++ b/variants/stm32/rak3172/rfswitch.h @@ -0,0 +1,7 @@ +// Pins from https://forum.rakwireless.com/t/rak3172-internal-schematic/4557/2 +// 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}; \ No newline at end of file diff --git a/variants/stm32/wio-e5/rfswitch.h b/variants/stm32/wio-e5/rfswitch.h new file mode 100644 index 000000000..3eadd9b5c --- /dev/null +++ b/variants/stm32/wio-e5/rfswitch.h @@ -0,0 +1,8 @@ +/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/ + * Wio-E5 module ONLY transmits through RFO_HP + * Receive: PA4=1, PA5=0 + * Transmit(high output power, SMPS mode): PA4=0, PA5=1 */ +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA4, PA5, 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}; From fa45660b7d29490982bb5c7c1c80d52282e61133 Mon Sep 17 00:00:00 2001 From: Davide Cavalca Date: Wed, 3 Sep 2025 23:25:45 -0700 Subject: [PATCH 28/81] Add TSL2561 sensor (#7675) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add TSL2561 sensor * Update platformio.ini Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/Telemetry/Sensor/TSL2561Sensor.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update protobufs * Clarify magic number in TSL2561Sensor.h * Use the correct version for Adafruit TSL2561 * Lint fixes * Fix typo --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Tom Fifield Co-authored-by: Thomas Göttgens --- platformio.ini | 2 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 12 +++++- src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 13 ++++++ .../Telemetry/Sensor/TSL2561Sensor.cpp | 41 +++++++++++++++++++ src/modules/Telemetry/Sensor/TSL2561Sensor.h | 23 +++++++++++ 7 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/modules/Telemetry/Sensor/TSL2561Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/TSL2561Sensor.h diff --git a/platformio.ini b/platformio.ini index b0f73bc09..275b09d8f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -177,6 +177,8 @@ lib_deps = adafruit/Adafruit PCT2075@1.0.5 # 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 ; (not included in native / portduino) [environmental_extra] diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index e46c6f623..470a416c0 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -80,6 +80,7 @@ class ScanI2C LTR553ALS, BHI260AP, BMM150, + TSL2561, DRV2605 } DeviceType; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 9aef9defe..5cb4fca32 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -461,7 +461,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address); SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address); - SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address); + case TSL25911_ADDR: + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1); + if (registerValue == 0x50) { + type = TSL2591; + logFoundDevice("TSL25911", (uint8_t)addr.address); + } else { + type = TSL2561; + logFoundDevice("TSL2561", (uint8_t)addr.address); + } + break; + SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address); diff --git a/src/main.cpp b/src/main.cpp index 5b223a824..6667f9f17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -743,6 +743,7 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); + scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2561, meshtastic_TelemetrySensorType_TSL2561); i2cScanner.reset(); #endif diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8926b171c..c90d9250f 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -198,6 +198,13 @@ T1000xSensor t1000xSensor; IndicatorSensor indicatorSensor; #endif +#if __has_include() +#include "Sensor/TSL2561Sensor.h" +TSL2561Sensor tsl2561Sensor; +#else +NullSensor tsl2561Sensor; +#endif + #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -296,6 +303,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = max17048Sensor.runOnce(); if (cgRadSens.hasSensor()) result = cgRadSens.runOnce(); + if (tsl2561Sensor.hasSensor()) + result = tsl2561Sensor.runOnce(); if (pct2075Sensor.hasSensor()) result = pct2075Sensor.runOnce(); // this only works on the wismesh hub with the solar option. This is not an I2C sensor, so we don't need the @@ -642,6 +651,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && nau7802Sensor.getMetrics(m); hasSensor = true; } + if (tsl2561Sensor.hasSensor()) { + valid = valid && tsl2561Sensor.getMetrics(m); + hasSensor = true; + } if (aht10Sensor.hasSensor()) { if (!bmp280Sensor.hasSensor() && !bmp3xxSensor.hasSensor()) { valid = valid && aht10Sensor.getMetrics(m); diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp new file mode 100644 index 000000000..9f3b7e460 --- /dev/null +++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp @@ -0,0 +1,41 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TSL2561Sensor.h" +#include "TelemetrySensor.h" +#include + +TSL2561Sensor::TSL2561Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL2561, "TSL2561") {} + +int32_t TSL2561Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second); + + return initI2CSensor(); +} + +void TSL2561Sensor::setup() +{ + tsl.setGain(TSL2561_GAIN_1X); + tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS); +} + +bool TSL2561Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + measurement->variant.environment_metrics.has_lux = true; + sensors_event_t event; + tsl.getEvent(&event); + measurement->variant.environment_metrics.lux = event.light; + LOG_INFO("Lux: %f", measurement->variant.environment_metrics.lux); + + return true; +} + +#endif diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.h b/src/modules/Telemetry/Sensor/TSL2561Sensor.h new file mode 100644 index 000000000..0329becd8 --- /dev/null +++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.h @@ -0,0 +1,23 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class TSL2561Sensor : public TelemetrySensor +{ + private: + // The magic number is a sensor id, the actual value doesn't matter + Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_LOW, 12345); + + protected: + virtual void setup() override; + + public: + TSL2561Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; +#endif From 361771c9bbe84339f8e69c493f40efdb758b4dd2 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Thu, 4 Sep 2025 16:28:53 +1000 Subject: [PATCH 29/81] chore(deps): update meshtastic/device-ui digest to 10f0244 (#7840) (#7851) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> From 521fbc44b4ff39a070779b417c01fd424358ec26 Mon Sep 17 00:00:00 2001 From: Marco Veneziano Date: Thu, 4 Sep 2025 08:31:16 +0200 Subject: [PATCH 30/81] Fix INA3221 higher current wrong readings (#7607) * chore(deps): update meshtastic/device-ui digest to 10f0244 (#7840) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * use branch of ina3221 library with fixes * using commit hash instead of branch name --------- 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 275b09d8f..61880c709 100644 --- a/platformio.ini +++ b/platformio.ini @@ -157,8 +157,8 @@ lib_deps = 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 - # renovate: datasource=github-tags depName=INA3221 packageName=KodinLanewave/INA3221 - https://github.com/KodinLanewave/INA3221/archive/1.0.1.zip + # renovate: datasource=github-tags depName=INA3221 packageName=sgtwilko/INA3221 + https://github.com/sgtwilko/INA3221#bb03d7e9bfcc74fc798838a54f4f99738f29fc6a # renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass mprograms/QMC5883LCompass@1.2.3 # renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU From 0be21d90c1c38da4ba5869caec0ad87bb840515a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:14:22 -0500 Subject: [PATCH 31/81] chore(deps): update actions/stale action to v10 (#7846) 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 5a11fdfa8..32e2c2c8b 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@v9.1.0 + uses: actions/stale@v10.0.0 with: days-before-stale: 45 exempt-issue-labels: pinned,3.0 From ced334d13bdc494d7af22a89f0c284544b080274 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:14:47 -0500 Subject: [PATCH 32/81] Automated version bumps (#7843) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 7 +++++-- version.properties | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index bebbc285e..108ca4910 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.9 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.8 diff --git a/debian/changelog b/debian/changelog index 3bb0de79c..29841d0db 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -meshtasticd (2.7.8.0) UNRELEASED; urgency=medium +meshtasticd (2.7.9.0) UNRELEASED; urgency=medium [ Austin Lane ] * Initial packaging @@ -44,4 +44,7 @@ meshtasticd (2.7.8.0) UNRELEASED; urgency=medium [ ] * GitHub Actions Automatic version bump - -- Sat, 30 Aug 2025 00:26:04 +0000 + [ ] + * GitHub Actions Automatic version bump + + -- Wed, 03 Sep 2025 23:39:17 +0000 diff --git a/version.properties b/version.properties index 506675fa8..cbf8265d9 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 8 +build = 9 From 4dfc062abd81343ded2dc356073c79ca8b6ca546 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:14:47 -0500 Subject: [PATCH 33/81] Automated version bumps (#7843) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 7 +++++-- version.properties | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index bebbc285e..108ca4910 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.9 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.8 diff --git a/debian/changelog b/debian/changelog index 3bb0de79c..29841d0db 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -meshtasticd (2.7.8.0) UNRELEASED; urgency=medium +meshtasticd (2.7.9.0) UNRELEASED; urgency=medium [ Austin Lane ] * Initial packaging @@ -44,4 +44,7 @@ meshtasticd (2.7.8.0) UNRELEASED; urgency=medium [ ] * GitHub Actions Automatic version bump - -- Sat, 30 Aug 2025 00:26:04 +0000 + [ ] + * GitHub Actions Automatic version bump + + -- Wed, 03 Sep 2025 23:39:17 +0000 diff --git a/version.properties b/version.properties index 506675fa8..cbf8265d9 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 8 +build = 9 From 55c23dec13b7d7139ecd58684714bc207bf45148 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:15:47 -0500 Subject: [PATCH 34/81] Upgrade trunk (#7853) 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 874715638..e10e20a04 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -9,12 +9,12 @@ plugins: lint: enabled: - checkov@3.2.469 - - renovate@41.93.2 + - renovate@41.94.0 - prettier@3.6.2 - trufflehog@3.90.5 - yamllint@1.37.1 - bandit@1.8.6 - - trivy@0.65.0 + - trivy@0.66.0 - taplo@0.10.0 - ruff@0.12.11 - isort@6.0.1 @@ -23,7 +23,7 @@ lint: - svgo@4.0.0 - actionlint@1.7.7 - flake8@7.3.0 - - hadolint@2.12.1-beta + - hadolint@2.13.1 - shfmt@3.6.0 - shellcheck@0.11.0 - black@25.1.0 From cc37535b2d110a0b9e29ea944354bc3b0e2c6fd3 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 4 Sep 2025 06:16:38 -0500 Subject: [PATCH 35/81] Enable bmx160 on native (#7844) --- arch/portduino/portduino.ini | 2 ++ src/motion/BMX160Sensor.h | 2 +- variants/nrf52840/rak4631/platformio.ini | 1 + variants/nrf52840/rak4631_eth_gw/platformio.ini | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index a6c1dff66..95c3bf3d9 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -31,6 +31,8 @@ lib_deps = 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 + https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip build_flags = ${arduino_base.build_flags} diff --git a/src/motion/BMX160Sensor.h b/src/motion/BMX160Sensor.h index d0efa5ae6..ddca5767c 100755 --- a/src/motion/BMX160Sensor.h +++ b/src/motion/BMX160Sensor.h @@ -7,7 +7,7 @@ #if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C -#if defined(RAK_4631) && !defined(RAK2560) && __has_include() +#if !defined(RAK2560) && __has_include() #include "Fusion/Fusion.h" #include diff --git a/variants/nrf52840/rak4631/platformio.ini b/variants/nrf52840/rak4631/platformio.ini index 83feaa06c..6bf5f44cb 100644 --- a/variants/nrf52840/rak4631/platformio.ini +++ b/variants/nrf52840/rak4631/platformio.ini @@ -22,6 +22,7 @@ lib_deps = https://github.com/RAKWireless/RAK13800-W5100S/archive/1.0.2.zip rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 beegee-tokyo/RAK12035_SoilMoisture@^1.0.4 + # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) diff --git a/variants/nrf52840/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini index 79cdb28c7..4be8843a2 100644 --- a/variants/nrf52840/rak4631_eth_gw/platformio.ini +++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini @@ -31,7 +31,8 @@ lib_deps = melopero/Melopero RV3028@^1.1.0 https://github.com/RAKWireless/RAK13800-W5100S/archive/1.0.2.zip rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 - https://github.com/meshtastic/RAK12034-BMX160/archive/4821355fb10390ba8557dc43ca29a023bcfbb9d9.zip + # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main + https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip bblanchon/ArduinoJson @ 6.21.4 ; 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 f994eb185f2d33190f43eb76c3104ab63e9693ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:17:11 -0500 Subject: [PATCH 36/81] chore(deps): update actions/setup-python action to v6 (#7849) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/actions/setup-base/action.yml | 2 +- .github/workflows/main_matrix.yml | 8 ++++---- .github/workflows/package_pio_deps.yml | 2 +- .github/workflows/release_channels.yml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml index 5c1c453dd..350ca290c 100644 --- a/.github/actions/setup-base/action.yml +++ b/.github/actions/setup-base/action.yml @@ -23,7 +23,7 @@ runs: sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.x cache: pip diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ed14907dc..66143cc01 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: 3.x cache: pip @@ -370,7 +370,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.x @@ -439,7 +439,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.x @@ -494,7 +494,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.x diff --git a/.github/workflows/package_pio_deps.yml b/.github/workflows/package_pio_deps.yml index 13d3d1b4e..d8ff6e631 100644 --- a/.github/workflows/package_pio_deps.yml +++ b/.github/workflows/package_pio_deps.yml @@ -31,7 +31,7 @@ jobs: repository: ${{github.event.pull_request.head.repo.full_name}} - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.x diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml index ccd99e792..486f4b1a6 100644 --- a/.github/workflows/release_channels.yml +++ b/.github/workflows/release_channels.yml @@ -63,7 +63,7 @@ jobs: uses: actions/checkout@v5 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.x From 26813326783a22a0ec96b3bc46a437006eb4240c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 06:17:23 -0500 Subject: [PATCH 37/81] chore(deps): update actions/setup-node action to v5 (#7848) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 52f180aa2..942659348 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,7 @@ jobs: pio upgrade - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: 22 From fe329892def991b842c2efd6dcc55217a1810086 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Thu, 4 Sep 2025 19:18:28 +0800 Subject: [PATCH 38/81] feat: New ESP32 variant 9m2ibr_aprs_lora_tracker (#7828) 9M2IBR APRS LoRa Tracker: ESP32-WROOM-32 + EBYTE E22-400M30S https://shopee.com.my/product/1095224/21692283917 Originally developed for LoRa_APRS_iGate and GPIO assignment is similar to https://github.com/richonguzman/LoRa_APRS_iGate/blob/main/variants/ESP32_DIY_1W_LoRa_Mesh_V1_2/board_pinout.h Signed-off-by: Andrew Yong --- .../9m2ibr_aprs_lora_tracker/platformio.ini | 12 +++ .../diy/9m2ibr_aprs_lora_tracker/variant.h | 74 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini create mode 100644 variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini new file mode 100644 index 000000000..809599212 --- /dev/null +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/platformio.ini @@ -0,0 +1,12 @@ +; 9M2IBR APRS LoRa Tracker: ESP32-WROOM-32 + EBYTE E22-400M30S +; https://shopee.com.my/product/1095224/21692283917 +[env:9m2ibr_aprs_lora_tracker] +extends = esp32_base +board = esp32doit-devkit-v1 +board_level = extra +build_flags = + ${esp32_base.build_flags} + -D PRIVATE_HW + -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 diff --git a/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h new file mode 100644 index 000000000..037933140 --- /dev/null +++ b/variants/esp32/diy/9m2ibr_aprs_lora_tracker/variant.h @@ -0,0 +1,74 @@ +/* + + 9M2IBR APRS LoRa Tracker: ESP32-WROOM-32 + EBYTE E22-400M30S + https://shopee.com.my/product/1095224/21692283917 + + Originally developed for LoRa_APRS_iGate and GPIO is similar to + https://github.com/richonguzman/LoRa_APRS_iGate/blob/main/variants/ESP32_DIY_1W_LoRa_Mesh_V1_2/board_pinout.h + +*/ + +// OLED (may be different controllers depending on screen size) +#define I2C_SDA 21 +#define I2C_SCL 22 +#define HAS_SCREEN 1 // Generates randomized BLE pin + +// GNSS: Ai-Thinker GP-02 BDS/GNSS module +#define GPS_RX_PIN 16 +#define GPS_TX_PIN 17 + +// Button +#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 + +// Buzzer +#define PIN_BUZZER 33 + +// Battery sense +#define BATTERY_PIN 35 +#define ADC_MULTIPLIER 2.01 // 100k + 100k, and add 1% tolerance +#define ADC_CHANNEL ADC1_GPIO35_CHANNEL +#define BATTERY_SENSE_RESOLUTION_BITS ADC_RESOLUTION + +// SPI +#define LORA_SCK 18 +#define LORA_MISO 19 +#define LORA_MOSI 23 + +// LoRa +#define LORA_CS 5 +#define LORA_DIO0 26 // a No connect on the SX1262/SX1268 module +#define LORA_RESET 27 // RST for SX1276, and for SX1262/SX1268 +#define LORA_DIO1 12 // IRQ for SX1262/SX1268 +#define LORA_DIO2 RADIOLIB_NC // BUSY for SX1262/SX1268 +#define LORA_DIO3 // NC, but used as TCXO supply by E22 module +#define LORA_RXEN 32 // RF switch RX (and E22 LNA) control by ESP32 GPIO +#define LORA_TXEN 25 // RF switch TX (and E22 PA) control by ESP32 GPIO + +// RX/TX for RFM95/SX127x +#define RF95_RXEN LORA_RXEN +#define RF95_TXEN LORA_TXEN +// #define RF95_TCXO + +// common pinouts for SX126X modules +#define SX126X_CS 5 +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_RXEN LORA_RXEN +#define SX126X_TXEN LORA_TXEN + +// Support alternative modules if soldered in place of E22 +#define USE_RF95 // RFM95/SX127x +#define USE_SX1262 +#define USE_SX1268 +#define USE_LLCC68 + +// E22 TCXO support +#ifdef EBYTE_E22 +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL +#endif From 09a0df3a1f1fc98e9692169e307f0d5954e97ec4 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Thu, 4 Sep 2025 06:16:38 -0500 Subject: [PATCH 39/81] Enable bmx160 on native (#7844) --- arch/portduino/portduino.ini | 2 ++ src/motion/BMX160Sensor.h | 2 +- variants/nrf52840/rak4631/platformio.ini | 1 + variants/nrf52840/rak4631_eth_gw/platformio.ini | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini index a6c1dff66..95c3bf3d9 100644 --- a/arch/portduino/portduino.ini +++ b/arch/portduino/portduino.ini @@ -31,6 +31,8 @@ lib_deps = 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 + https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip build_flags = ${arduino_base.build_flags} diff --git a/src/motion/BMX160Sensor.h b/src/motion/BMX160Sensor.h index d0efa5ae6..ddca5767c 100755 --- a/src/motion/BMX160Sensor.h +++ b/src/motion/BMX160Sensor.h @@ -7,7 +7,7 @@ #if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C -#if defined(RAK_4631) && !defined(RAK2560) && __has_include() +#if !defined(RAK2560) && __has_include() #include "Fusion/Fusion.h" #include diff --git a/variants/nrf52840/rak4631/platformio.ini b/variants/nrf52840/rak4631/platformio.ini index 83feaa06c..6bf5f44cb 100644 --- a/variants/nrf52840/rak4631/platformio.ini +++ b/variants/nrf52840/rak4631/platformio.ini @@ -22,6 +22,7 @@ lib_deps = https://github.com/RAKWireless/RAK13800-W5100S/archive/1.0.2.zip rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 beegee-tokyo/RAK12035_SoilMoisture@^1.0.4 + # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) diff --git a/variants/nrf52840/rak4631_eth_gw/platformio.ini b/variants/nrf52840/rak4631_eth_gw/platformio.ini index 79cdb28c7..4be8843a2 100644 --- a/variants/nrf52840/rak4631_eth_gw/platformio.ini +++ b/variants/nrf52840/rak4631_eth_gw/platformio.ini @@ -31,7 +31,8 @@ lib_deps = melopero/Melopero RV3028@^1.1.0 https://github.com/RAKWireless/RAK13800-W5100S/archive/1.0.2.zip rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2 - https://github.com/meshtastic/RAK12034-BMX160/archive/4821355fb10390ba8557dc43ca29a023bcfbb9d9.zip + # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main + https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip bblanchon/ArduinoJson @ 6.21.4 ; 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 26bcc9627df0496fa81411d68528e018fdabb784 Mon Sep 17 00:00:00 2001 From: TN <44137240+TN666@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:26:04 +0800 Subject: [PATCH 40/81] merge create_test_packet duplicate usage into a shared function (#7752) --- .../ports/test_encrypted.cpp | 51 +++++-------------- .../test_meshpacket_serializer/test_helpers.h | 9 +++- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/test/test_meshpacket_serializer/ports/test_encrypted.cpp b/test/test_meshpacket_serializer/ports/test_encrypted.cpp index 24866654a..37cfc1626 100644 --- a/test/test_meshpacket_serializer/ports/test_encrypted.cpp +++ b/test/test_meshpacket_serializer/ports/test_encrypted.cpp @@ -1,30 +1,7 @@ #include "../test_helpers.h" -// test data initialization -const int from = 0x11223344; -const int to = 0x55667788; -const int id = 0x9999; - -// Helper function to create a test encrypted packet -meshtastic_MeshPacket create_test_encrypted_packet(uint32_t from, uint32_t to, uint32_t id, const char *data) -{ - meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero; - packet.from = from; - packet.to = to; - packet.id = id; - packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag; - - if (data) { - packet.encrypted.size = strlen(data); - memcpy(packet.encrypted.bytes, data, packet.encrypted.size); - } - - return packet; -} - -// Comprehensive helper function for all encrypted packet assertions -void assert_encrypted_packet(const std::string &json, uint32_t expected_from, uint32_t expected_to, uint32_t expected_id, - size_t expected_size) +// Helper function for all encrypted packet assertions +void assert_encrypted_packet(const std::string &json, meshtastic_MeshPacket packet) { // Parse and validate JSON TEST_ASSERT_TRUE(json.length() > 0); @@ -37,24 +14,24 @@ void assert_encrypted_packet(const std::string &json, uint32_t expected_from, ui // Assert basic packet fields TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end()); - TEST_ASSERT_EQUAL(expected_from, (uint32_t)jsonObj.at("from")->AsNumber()); + TEST_ASSERT_EQUAL(packet.from, (uint32_t)jsonObj.at("from")->AsNumber()); TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end()); - TEST_ASSERT_EQUAL(expected_to, (uint32_t)jsonObj.at("to")->AsNumber()); + TEST_ASSERT_EQUAL(packet.to, (uint32_t)jsonObj.at("to")->AsNumber()); TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end()); - TEST_ASSERT_EQUAL(expected_id, (uint32_t)jsonObj.at("id")->AsNumber()); + TEST_ASSERT_EQUAL(packet.id, (uint32_t)jsonObj.at("id")->AsNumber()); // Assert encrypted data fields TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end()); TEST_ASSERT_TRUE(jsonObj.at("bytes")->IsString()); TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end()); - TEST_ASSERT_EQUAL(expected_size, (int)jsonObj.at("size")->AsNumber()); + TEST_ASSERT_EQUAL(packet.encrypted.size, (int)jsonObj.at("size")->AsNumber()); // Assert hex encoding std::string encrypted_hex = jsonObj["bytes"]->AsString(); - TEST_ASSERT_EQUAL(expected_size * 2, encrypted_hex.length()); + TEST_ASSERT_EQUAL(packet.encrypted.size * 2, encrypted_hex.length()); delete root; } @@ -63,20 +40,20 @@ void assert_encrypted_packet(const std::string &json, uint32_t expected_from, ui void test_encrypted_packet_serialization() { const char *data = "encrypted_payload_data"; - - meshtastic_MeshPacket packet = create_test_encrypted_packet(from, to, id, data); + meshtastic_MeshPacket packet = + create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(data), strlen(data), + meshtastic_MeshPacket_encrypted_tag); std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet); - assert_encrypted_packet(json, from, to, id, strlen(data)); + assert_encrypted_packet(json, packet); } // Test empty encrypted packet void test_empty_encrypted_packet() { - const char *data = ""; - - meshtastic_MeshPacket packet = create_test_encrypted_packet(from, to, id, data); + meshtastic_MeshPacket packet = + create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0, meshtastic_MeshPacket_encrypted_tag); std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet); - assert_encrypted_packet(json, from, to, id, strlen(data)); + assert_encrypted_packet(json, packet); } diff --git a/test/test_meshpacket_serializer/test_helpers.h b/test/test_meshpacket_serializer/test_helpers.h index 630e059bc..12245b85d 100644 --- a/test/test_meshpacket_serializer/test_helpers.h +++ b/test/test_meshpacket_serializer/test_helpers.h @@ -11,7 +11,8 @@ #include // Helper function to create a test packet with the given port and payload -static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const uint8_t *payload, size_t payload_size) +static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const uint8_t *payload, size_t payload_size, + int payload_variant = meshtastic_MeshPacket_decoded_tag) { meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero; @@ -29,8 +30,12 @@ static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const u packet.delayed = meshtastic_MeshPacket_Delayed_NO_DELAY; // Set decoded variant - packet.which_payload_variant = meshtastic_MeshPacket_decoded_tag; + packet.which_payload_variant = payload_variant; packet.decoded.portnum = port; + if (payload_variant == meshtastic_MeshPacket_encrypted_tag && payload) { + packet.encrypted.size = payload_size; + memcpy(packet.encrypted.bytes, payload, packet.encrypted.size); + } memcpy(packet.decoded.payload.bytes, payload, payload_size); packet.decoded.payload.size = payload_size; packet.decoded.want_response = false; From 289f90bdbec72096ce9fb99eaf5587827245126a Mon Sep 17 00:00:00 2001 From: TN <44137240+TN666@users.noreply.github.com> Date: Thu, 4 Sep 2025 19:26:04 +0800 Subject: [PATCH 41/81] merge create_test_packet duplicate usage into a shared function (#7752) --- .../ports/test_encrypted.cpp | 69 +++++++++++++++++++ .../test_meshpacket_serializer/test_helpers.h | 9 ++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/test/test_meshpacket_serializer/ports/test_encrypted.cpp b/test/test_meshpacket_serializer/ports/test_encrypted.cpp index 557ee7a49..9efc2fb1b 100644 --- a/test/test_meshpacket_serializer/ports/test_encrypted.cpp +++ b/test/test_meshpacket_serializer/ports/test_encrypted.cpp @@ -1,5 +1,63 @@ #include "../test_helpers.h" +// test data initialization +const int from = 0x11223344; +const int to = 0x55667788; +const int id = 0x9999; + +// Helper function to create a test encrypted packet +meshtastic_MeshPacket create_test_encrypted_packet(uint32_t from, uint32_t to, uint32_t id, const char *data) +{ + meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero; + packet.from = from; + packet.to = to; + packet.id = id; + packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag; + + if (data) { + packet.encrypted.size = strlen(data); + memcpy(packet.encrypted.bytes, data, packet.encrypted.size); + } + + return packet; +} + +// Helper function for all encrypted packet assertions +void assert_encrypted_packet(const std::string &json, meshtastic_MeshPacket packet) +{ + // Parse and validate JSON + TEST_ASSERT_TRUE(json.length() > 0); + + JSONValue *root = JSON::Parse(json.c_str()); + TEST_ASSERT_NOT_NULL(root); + TEST_ASSERT_TRUE(root->IsObject()); + + JSONObject jsonObj = root->AsObject(); + + // Assert basic packet fields + TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end()); + TEST_ASSERT_EQUAL(packet.from, (uint32_t)jsonObj.at("from")->AsNumber()); + + TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end()); + TEST_ASSERT_EQUAL(packet.to, (uint32_t)jsonObj.at("to")->AsNumber()); + + TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end()); + TEST_ASSERT_EQUAL(packet.id, (uint32_t)jsonObj.at("id")->AsNumber()); + + // Assert encrypted data fields + TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end()); + TEST_ASSERT_TRUE(jsonObj.at("bytes")->IsString()); + + TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end()); + TEST_ASSERT_EQUAL(packet.encrypted.size, (int)jsonObj.at("size")->AsNumber()); + + // Assert hex encoding + std::string encrypted_hex = jsonObj["bytes"]->AsString(); + TEST_ASSERT_EQUAL(packet.encrypted.size * 2, encrypted_hex.length()); + + delete root; +} + // Test encrypted packet serialization void test_encrypted_packet_serialization() { @@ -48,3 +106,14 @@ void test_encrypted_packet_serialization() delete root; } + +// Test empty encrypted packet +void test_empty_encrypted_packet() +{ + const char *data = ""; + + meshtastic_MeshPacket packet = create_test_encrypted_packet(from, to, id, data); + std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet); + + assert_encrypted_packet(json, packet); +} diff --git a/test/test_meshpacket_serializer/test_helpers.h b/test/test_meshpacket_serializer/test_helpers.h index 630e059bc..12245b85d 100644 --- a/test/test_meshpacket_serializer/test_helpers.h +++ b/test/test_meshpacket_serializer/test_helpers.h @@ -11,7 +11,8 @@ #include // Helper function to create a test packet with the given port and payload -static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const uint8_t *payload, size_t payload_size) +static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const uint8_t *payload, size_t payload_size, + int payload_variant = meshtastic_MeshPacket_decoded_tag) { meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero; @@ -29,8 +30,12 @@ static meshtastic_MeshPacket create_test_packet(meshtastic_PortNum port, const u packet.delayed = meshtastic_MeshPacket_Delayed_NO_DELAY; // Set decoded variant - packet.which_payload_variant = meshtastic_MeshPacket_decoded_tag; + packet.which_payload_variant = payload_variant; packet.decoded.portnum = port; + if (payload_variant == meshtastic_MeshPacket_encrypted_tag && payload) { + packet.encrypted.size = payload_size; + memcpy(packet.encrypted.bytes, payload, packet.encrypted.size); + } memcpy(packet.decoded.payload.bytes, payload, payload_size); packet.decoded.payload.size = payload_size; packet.decoded.want_response = false; From 5b63bd9331e1099c349002e65ac293dcca901bf3 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Thu, 4 Sep 2025 06:12:47 +0100 Subject: [PATCH 42/81] Add RF switch settings for STM32WL variants (#7813) * Add RF switch settings for STM32WL variants * Shuffle ifdefs in STM32WLE5JCInterface to make it not get built by other targets --- src/mesh/STM32WLE5JCInterface.cpp | 6 +++--- src/mesh/STM32WLE5JCInterface.h | 13 ++----------- variants/stm32/CDEBYTE_E77-MBL/platformio.ini | 2 -- variants/stm32/CDEBYTE_E77-MBL/rfswitch.h | 9 +++++++++ variants/stm32/CDEBYTE_E77-MBL/variant.h | 1 - variants/stm32/rak3172/platformio.ini | 2 +- variants/stm32/rak3172/rfswitch.h | 7 +++++++ variants/stm32/wio-e5/rfswitch.h | 8 ++++++++ 8 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 variants/stm32/CDEBYTE_E77-MBL/rfswitch.h create mode 100644 variants/stm32/rak3172/rfswitch.h create mode 100644 variants/stm32/wio-e5/rfswitch.h diff --git a/src/mesh/STM32WLE5JCInterface.cpp b/src/mesh/STM32WLE5JCInterface.cpp index d7bc37466..f6e4b3512 100644 --- a/src/mesh/STM32WLE5JCInterface.cpp +++ b/src/mesh/STM32WLE5JCInterface.cpp @@ -1,13 +1,13 @@ -#include "STM32WLE5JCInterface.h" #include "configuration.h" + +#ifdef ARCH_STM32WL +#include "STM32WLE5JCInterface.h" #include "error.h" #ifndef STM32WLx_MAX_POWER #define STM32WLx_MAX_POWER 22 #endif -#ifdef ARCH_STM32WL - STM32WLE5JCInterface::STM32WLE5JCInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) : SX126xInterface(hal, cs, irq, rst, busy) diff --git a/src/mesh/STM32WLE5JCInterface.h b/src/mesh/STM32WLE5JCInterface.h index 0c8140290..ee935375e 100644 --- a/src/mesh/STM32WLE5JCInterface.h +++ b/src/mesh/STM32WLE5JCInterface.h @@ -1,8 +1,8 @@ #pragma once -#include "SX126xInterface.h" - #ifdef ARCH_STM32WL +#include "SX126xInterface.h" +#include "rfswitch.h" /** * Our adapter for STM32WLE5JC radios @@ -16,13 +16,4 @@ class STM32WLE5JCInterface : public SX126xInterface virtual bool init() override; }; -/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/ - * Wio-E5 module ONLY transmits through RFO_HP - * Receive: PA4=1, PA5=0 - * Transmit(high output power, SMPS mode): PA4=0, PA5=1 */ -static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA4, PA5, 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}; - #endif // ARCH_STM32WL \ No newline at end of file diff --git a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini index c011f62c9..290982405 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini +++ b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini @@ -12,7 +12,5 @@ build_flags = -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 -DMESHTASTIC_EXCLUDE_I2C=1 -DMESHTASTIC_EXCLUDE_GPS=1 - ;-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF - ;-DCFG_DEBUG upload_port = stlink \ No newline at end of file diff --git a/variants/stm32/CDEBYTE_E77-MBL/rfswitch.h b/variants/stm32/CDEBYTE_E77-MBL/rfswitch.h new file mode 100644 index 000000000..daf4aaaf9 --- /dev/null +++ b/variants/stm32/CDEBYTE_E77-MBL/rfswitch.h @@ -0,0 +1,9 @@ +// From E77-900M22S Product Specification +// https://www.cdebyte.com/pdf-down.aspx?id=2963 +// Note 1: PA6 and PA7 pins are used as internal control RF switches of the module, PA6 = RF_TXEN, PA7 = RF_RXEN, RF_TXEN=1 +// RF_RXEN=0 is the transmit channel, and RF_TXEN=0 RF_RXEN=1 is the receiving channel + +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA7, PA6, 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}; \ No newline at end of file diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index 52801dac7..317f44489 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -18,5 +18,4 @@ Do not expect a working Meshtastic device with this target. #define LED_PIN PB4 // LED1 // #define LED_PIN PB3 // LED2 #define LED_STATE_ON 1 - #endif diff --git a/variants/stm32/rak3172/platformio.ini b/variants/stm32/rak3172/platformio.ini index a12b9f21c..7fc6c7cba 100644 --- a/variants/stm32/rak3172/platformio.ini +++ b/variants/stm32/rak3172/platformio.ini @@ -15,5 +15,5 @@ build_flags = -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 -DMESHTASTIC_EXCLUDE_I2C=1 -DMESHTASTIC_EXCLUDE_GPS=1 - ;-DCFG_DEBUG + upload_port = stlink diff --git a/variants/stm32/rak3172/rfswitch.h b/variants/stm32/rak3172/rfswitch.h new file mode 100644 index 000000000..2dced3c7c --- /dev/null +++ b/variants/stm32/rak3172/rfswitch.h @@ -0,0 +1,7 @@ +// Pins from https://forum.rakwireless.com/t/rak3172-internal-schematic/4557/2 +// 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}; \ No newline at end of file diff --git a/variants/stm32/wio-e5/rfswitch.h b/variants/stm32/wio-e5/rfswitch.h new file mode 100644 index 000000000..3eadd9b5c --- /dev/null +++ b/variants/stm32/wio-e5/rfswitch.h @@ -0,0 +1,8 @@ +/* https://wiki.seeedstudio.com/LoRa-E5_STM32WLE5JC_Module/ + * Wio-E5 module ONLY transmits through RFO_HP + * Receive: PA4=1, PA5=0 + * Transmit(high output power, SMPS mode): PA4=0, PA5=1 */ +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PA4, PA5, 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}; From e4c7fca716e5095beb2f36effeb67b7645b3bccf Mon Sep 17 00:00:00 2001 From: "Daniel.Cao" <144674500+DanielCao0@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:20:19 +0800 Subject: [PATCH 43/81] Add RAK WisMesh Tap V2 (ESP32S3) Hardware Variant (#7741) * Add initial variant and platformio configuration for RAK WISMESHTAP V2 * Add initial variant and platformio configuration for rak wismesh tap v2 * Remove unnecessary Meshtastic build flags from rak_wismesh_tap_v2 configuration * Enable LGFX button support in rak_wismesh_tap_v2 configuration * Revert "Enable LGFX button support in rak_wismesh_tap_v2 configuration" This reverts commit 2bd2c1a03b1b8a224c440049b7aff8a15bb54dbf. --------- Co-authored-by: Daniel.Cao --- .../esp32s3/rak_wismesh_tap_v2/pins_arduino.h | 28 ++++++ .../esp32s3/rak_wismesh_tap_v2/platformio.ini | 87 +++++++++++++++++++ variants/esp32s3/rak_wismesh_tap_v2/variant.h | 71 +++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h create mode 100644 variants/esp32s3/rak_wismesh_tap_v2/platformio.ini create mode 100644 variants/esp32s3/rak_wismesh_tap_v2/variant.h diff --git a/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h new file mode 100644 index 000000000..15a26e991 --- /dev/null +++ b/variants/esp32s3/rak_wismesh_tap_v2/pins_arduino.h @@ -0,0 +1,28 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "variant.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 = 9; +static const uint8_t SCL = 40; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 12; +static const uint8_t MOSI = 11; +static const uint8_t MISO = 10; +static const uint8_t SCK = 13; + +#define SPI_MOSI (11) +#define SPI_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/rak_wismesh_tap_v2/platformio.ini b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini new file mode 100644 index 000000000..8b86e0217 --- /dev/null +++ b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini @@ -0,0 +1,87 @@ +; rak_wismeshtap2 rak3112 + +[rak_wismeshtap_s3] +extends = esp32s3_base +board = wiscore_rak3312 +board_check = true +upload_protocol = esptool +board_build.partitions = default_8MB.csv + +build_flags = + ${esp32_base.build_flags} + -D RAK3312 + -D RAK_WISMESH_TAP_V2 + -I variants/esp32s3/rak_wismesh_tap_v2 + +lib_deps = + ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@^1.2.0 + +[ft5x06] +extends = mesh_tab_base +build_flags = + -D LGFX_TOUCH=FT5x06 + -D LGFX_TOUCH_I2C_FREQ=100000 + -D LGFX_TOUCH_I2C_PORT=0 + -D LGFX_TOUCH_I2C_ADDR=0x38 + -D LGFX_TOUCH_I2C_SDA=9 + -D LGFX_TOUCH_I2C_SCL=40 + -D LGFX_TOUCH_RST=-1 + -D LGFX_TOUCH_INT=39 + +[env:rak_wismesh_tap_v2-tft] +extends = rak_wismeshtap_s3 + +build_flags = + ${rak_wismeshtap_s3.build_flags} + -D CONFIG_ARDUHAL_ESP_LOG + -D CONFIG_ARDUHAL_LOG_COLORS=1 + -D CONFIG_DISABLE_HAL_LOCKS=1 + -D LV_LVGL_H_INCLUDE_SIMPLE + -D LV_CONF_INCLUDE_SIMPLE + -D LV_COMP_CONF_INCLUDE_SIMPLE + -D LV_USE_SYSMON=0 + -D LV_USE_PROFILER=0 + -D LV_USE_PERF_MONITOR=0 + -D LV_USE_MEM_MONITOR=0 + -D LV_USE_LOG=0 + -D LV_BUILD_TEST=0 + -D USE_LOG_DEBUG + -D LOG_DEBUG_INC=\"DebugConfiguration.h\" + -D RADIOLIB_SPI_PARANOID=0 + -D INPUTDRIVER_BUTTON_TYPE=0 + -D HAS_SDCARD + -D HAS_SCREEN=0 + -D HAS_TFT=1 + -D USE_PIN_BUZZER=PIN_BUZZER + -D RAM_SIZE=5120 + -D LGFX_DRIVER_TEMPLATE + -D LGFX_DRIVER=LGFX_GENERIC + -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_GENERIC.h\" + -D LGFX_PIN_SCK=13 + -D LGFX_PIN_MOSI=11 + -D LGFX_PIN_MISO=10 + -D LGFX_PIN_DC=42 + -D LGFX_PIN_CS=12 + -D LGFX_PIN_RST=-1 + -D LGFX_PIN_BL=41 + -D VIEW_320x240 + -D USE_PACKET_API + ${ft5x06.build_flags} + -D LGFX_SCREEN_WIDTH=240 + -D LGFX_SCREEN_HEIGHT=320 + -D LGFX_PANEL=ST7789 + -D LGFX_ROTATION=1 + -D LGFX_TOUCH_X_MIN=0 + -D LGFX_TOUCH_X_MAX=239 + -D LGFX_TOUCH_Y_MIN=0 + -D LGFX_TOUCH_Y_MAX=319 + -D LGFX_TOUCH_ROTATION=2 + -D LGFX_CFG_HOST=SPI3_HOST + -D MAP_FULL_REDRAW=1 + +lib_deps = + ${rak_wismeshtap_s3.lib_deps} + ${device-ui_base.lib_deps} + + diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h new file mode 100644 index 000000000..8468c557e --- /dev/null +++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h @@ -0,0 +1,71 @@ +#ifndef _VARIANT_RAK_WISMESHTAP_V2_H +#define _VARIANT_RAK_WISMESHTAP_V2_H + +#define I2C_SDA 9 +#define I2C_SCL 40 + +#define USE_SX1262 + +#define LORA_SCK 5 +#define LORA_MISO 3 +#define LORA_MOSI 6 +#define LORA_CS 7 +#define LORA_RESET 8 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 47 +#define SX126X_BUSY 48 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif + +#define SX126X_POWER_EN (4) + +#define PIN_POWER_EN PIN_3V3_EN +#define PIN_3V3_EN (14) + +#define LED_GREEN 46 +#define LED_BLUE 45 + +#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) + +#define LED_STATE_ON 1 // State when LED is litted + +#define HAS_GPS 1 +#define GPS_TX_PIN 43 +#define GPS_RX_PIN 44 + +#define SPI_MOSI (11) +#define SPI_SCK (13) +#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 +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_MULTIPLIER 1.667 + +#define PIN_BUZZER 38 + +#define HAS_SDCARD 1 +#define SDCARD_USE_SPI1 1 +#define SDCARD_CS 2 + +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 + +#define SD_SPI_FREQUENCY 50000000 + +#endif \ No newline at end of file From 7776ec15b6c14bfc761cd270a1e3c141a0156254 Mon Sep 17 00:00:00 2001 From: Davide Cavalca Date: Wed, 3 Sep 2025 23:25:45 -0700 Subject: [PATCH 44/81] Add TSL2561 sensor (#7675) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add TSL2561 sensor * Update platformio.ini Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/Telemetry/Sensor/TSL2561Sensor.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update protobufs * Clarify magic number in TSL2561Sensor.h * Use the correct version for Adafruit TSL2561 * Lint fixes * Fix typo --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Tom Fifield Co-authored-by: Thomas Göttgens --- platformio.ini | 2 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 12 +++++- src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 13 ++++++ .../Telemetry/Sensor/TSL2561Sensor.cpp | 41 +++++++++++++++++++ src/modules/Telemetry/Sensor/TSL2561Sensor.h | 23 +++++++++++ 7 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/modules/Telemetry/Sensor/TSL2561Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/TSL2561Sensor.h diff --git a/platformio.ini b/platformio.ini index b0f73bc09..275b09d8f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -177,6 +177,8 @@ lib_deps = adafruit/Adafruit PCT2075@1.0.5 # 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 ; (not included in native / portduino) [environmental_extra] diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index e46c6f623..470a416c0 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -80,6 +80,7 @@ class ScanI2C LTR553ALS, BHI260AP, BMM150, + TSL2561, DRV2605 } DeviceType; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 9aef9defe..5cb4fca32 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -461,7 +461,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address); SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address); - SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591", (uint8_t)addr.address); + case TSL25911_ADDR: + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1); + if (registerValue == 0x50) { + type = TSL2591; + logFoundDevice("TSL25911", (uint8_t)addr.address); + } else { + type = TSL2561; + logFoundDevice("TSL2561", (uint8_t)addr.address); + } + break; + SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632", (uint8_t)addr.address); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802", (uint8_t)addr.address); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address); diff --git a/src/main.cpp b/src/main.cpp index 8263a3144..401ea7592 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -741,6 +741,7 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); + scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::TSL2561, meshtastic_TelemetrySensorType_TSL2561); i2cScanner.reset(); #endif diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8926b171c..c90d9250f 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -198,6 +198,13 @@ T1000xSensor t1000xSensor; IndicatorSensor indicatorSensor; #endif +#if __has_include() +#include "Sensor/TSL2561Sensor.h" +TSL2561Sensor tsl2561Sensor; +#else +NullSensor tsl2561Sensor; +#endif + #define FAILED_STATE_SENSOR_READ_MULTIPLIER 10 #define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true @@ -296,6 +303,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = max17048Sensor.runOnce(); if (cgRadSens.hasSensor()) result = cgRadSens.runOnce(); + if (tsl2561Sensor.hasSensor()) + result = tsl2561Sensor.runOnce(); if (pct2075Sensor.hasSensor()) result = pct2075Sensor.runOnce(); // this only works on the wismesh hub with the solar option. This is not an I2C sensor, so we don't need the @@ -642,6 +651,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && nau7802Sensor.getMetrics(m); hasSensor = true; } + if (tsl2561Sensor.hasSensor()) { + valid = valid && tsl2561Sensor.getMetrics(m); + hasSensor = true; + } if (aht10Sensor.hasSensor()) { if (!bmp280Sensor.hasSensor() && !bmp3xxSensor.hasSensor()) { valid = valid && aht10Sensor.getMetrics(m); diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp new file mode 100644 index 000000000..9f3b7e460 --- /dev/null +++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.cpp @@ -0,0 +1,41 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TSL2561Sensor.h" +#include "TelemetrySensor.h" +#include + +TSL2561Sensor::TSL2561Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL2561, "TSL2561") {} + +int32_t TSL2561Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second); + + return initI2CSensor(); +} + +void TSL2561Sensor::setup() +{ + tsl.setGain(TSL2561_GAIN_1X); + tsl.setIntegrationTime(TSL2561_INTEGRATIONTIME_101MS); +} + +bool TSL2561Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + measurement->variant.environment_metrics.has_lux = true; + sensors_event_t event; + tsl.getEvent(&event); + measurement->variant.environment_metrics.lux = event.light; + LOG_INFO("Lux: %f", measurement->variant.environment_metrics.lux); + + return true; +} + +#endif diff --git a/src/modules/Telemetry/Sensor/TSL2561Sensor.h b/src/modules/Telemetry/Sensor/TSL2561Sensor.h new file mode 100644 index 000000000..0329becd8 --- /dev/null +++ b/src/modules/Telemetry/Sensor/TSL2561Sensor.h @@ -0,0 +1,23 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class TSL2561Sensor : public TelemetrySensor +{ + private: + // The magic number is a sensor id, the actual value doesn't matter + Adafruit_TSL2561_Unified tsl = Adafruit_TSL2561_Unified(TSL2561_ADDR_LOW, 12345); + + protected: + virtual void setup() override; + + public: + TSL2561Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; +#endif From 18000ccf21e85f120313171318206755f4374846 Mon Sep 17 00:00:00 2001 From: Marco Veneziano Date: Thu, 4 Sep 2025 08:31:16 +0200 Subject: [PATCH 45/81] Fix INA3221 higher current wrong readings (#7607) * chore(deps): update meshtastic/device-ui digest to 10f0244 (#7840) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * use branch of ina3221 library with fixes * using commit hash instead of branch name --------- 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 275b09d8f..61880c709 100644 --- a/platformio.ini +++ b/platformio.ini @@ -157,8 +157,8 @@ lib_deps = 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 - # renovate: datasource=github-tags depName=INA3221 packageName=KodinLanewave/INA3221 - https://github.com/KodinLanewave/INA3221/archive/1.0.1.zip + # renovate: datasource=github-tags depName=INA3221 packageName=sgtwilko/INA3221 + https://github.com/sgtwilko/INA3221#bb03d7e9bfcc74fc798838a54f4f99738f29fc6a # renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass mprograms/QMC5883LCompass@1.2.3 # renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU From f31fd34ce049d4720a2adc69101fb9fe2e8d1b62 Mon Sep 17 00:00:00 2001 From: Sam Duffield <136561674+samuel-duffield1@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:49:47 +0100 Subject: [PATCH 46/81] Add support for the Challenger rp2040 lora (#7826) * Firmware Built... awaiting parts for test * Add board_level key/value as per suggestion from vidplace7 * Trunk formatting applied --- .../challenger_2040_lora/pins_arduino.h | 79 +++++++++++++++++++ .../challenger_2040_lora/platformio.ini | 16 ++++ .../rp2040/challenger_2040_lora/variant.h | 39 +++++++++ 3 files changed, 134 insertions(+) create mode 100644 variants/rp2040/challenger_2040_lora/pins_arduino.h create mode 100644 variants/rp2040/challenger_2040_lora/platformio.ini create mode 100644 variants/rp2040/challenger_2040_lora/variant.h diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h new file mode 100644 index 000000000..ac472c07e --- /dev/null +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -0,0 +1,79 @@ +#pragma once + +#define PINS_COUNT (25u) +#define NUM_DIGITAL_PINS (25u) +#define NUM_ANALOG_INPUTS (4u) +#define NUM_ANALOG_OUTPUTS (0u) +#define ADC_RESOLUTION (12u) + +// LEDs +#define PIN_LED (24u) + +// Serial +#define PIN_SERIAL1_TX (16u) +#define PIN_SERIAL1_RX (17u) + +// SPI +#define PIN_SPI0_MISO (20u) +#define PIN_SPI0_MOSI (23u) +#define PIN_SPI0_SCK (22u) +#define PIN_SPI0_SS (21u) + +// Connected to LoRa module +#define PIN_SPI1_MISO (12u) +#define PIN_SPI1_MOSI (11u) +#define PIN_SPI1_SCK (10u) +#define PIN_SPI1_SS (9u) +#define RFM95W_SS (9u) +#define RFM95W_DIO0 (14u) +#define RFM95W_DIO1 (15u) +#define RFM95W_DIO2 (18u) +#define RFM95W_RST (13u) +#define RFM95W_SPI SPI1 + +// Wire +#define PIN_WIRE0_SDA (0u) +#define PIN_WIRE0_SCL (1u) + +// Not pinned out +#define PIN_WIRE1_SDA (31u) +#define PIN_WIRE1_SCL (31u) +#define PIN_SERIAL2_RX (31u) +#define PIN_SERIAL2_TX (31u) + +#define SERIAL_HOWMANY (1u) +#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); +static const uint8_t D3 = (23u); +static const uint8_t D4 = (22u); +static const uint8_t D5 = (2u); +static const uint8_t D6 = (3u); +static const uint8_t D7 = (0u); +static const uint8_t D8 = (1u); +static const uint8_t D9 = (4u); +static const uint8_t D10 = (5u); +static const uint8_t D11 = (6u); +static const uint8_t D12 = (7u); +static const uint8_t D13 = (8u); +static const uint8_t D14 = (13u); +static const uint8_t D15 = (14u); +static const uint8_t D16 = (15u); +static const uint8_t D17 = (18u); +static const uint8_t D18 = (24u); + +static const uint8_t A0 = (26u); +static const uint8_t A1 = (27u); +static const uint8_t A2 = (28u); +static const uint8_t A3 = (29u); +static const uint8_t A4 = (19u); +static const uint8_t A5 = (21u); + +#ifndef SS +#define SS PIN_SPI1_SS +#endif \ No newline at end of file diff --git a/variants/rp2040/challenger_2040_lora/platformio.ini b/variants/rp2040/challenger_2040_lora/platformio.ini new file mode 100644 index 000000000..4a709d650 --- /dev/null +++ b/variants/rp2040/challenger_2040_lora/platformio.ini @@ -0,0 +1,16 @@ +[env:challenger_2040_lora] +extends = rp2040_base +board = challenger_2040_lora +board_level = extra +upload_protocol = picotool +# add our variants files to the include and src paths +build_flags = + ${rp2040_base.build_flags} + -D PRIVATE_HW + -I variants/rp2040/challenger_2040_lora + -D DEBUG_RP2040_PORT=Serial + -D HW_SPI1_DEVICE +lib_deps = + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool diff --git a/variants/rp2040/challenger_2040_lora/variant.h b/variants/rp2040/challenger_2040_lora/variant.h new file mode 100644 index 000000000..552f90720 --- /dev/null +++ b/variants/rp2040/challenger_2040_lora/variant.h @@ -0,0 +1,39 @@ +// Define SS for compatibility with libraries expecting a default SPI chip select pin + +#define ARDUINO_ARCH_AVR + +#define EXT_NOTIFY_OUT 0xFFFFFFFF +#define BUTTON_PIN 0xFFFFFFFF + +#define LED_PIN PIN_LED + +#define USE_RF95 // RFM95/SX127x + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +// https://gitlab.com/invectorlabs/hw/challenger_rp2040_lora +#define LORA_SCK 10 // Clock +#define LORA_CS 9 // Chip Select +#define LORA_MOSI 11 // Serial Data Out +#define LORA_MISO 12 // Serial Data In + +#define LORA_RESET 13 // Reset + +#define LORA_DIO0 14 // DIO0 +#define LORA_DIO1 15 // DIO1 +#define LORA_DIO2 18 // DIO2 +#define LORA_DIO3 0xFFFFFFFF // Not connected +#define LORA_DIO4 0xFFFFFFFF // Not connected +#define LORA_DIO5 0xFFFFFFFF // Not connected + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +// #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file From 4881362340c47aad1225dc2ce289865ae5d16301 Mon Sep 17 00:00:00 2001 From: Sam Duffield <136561674+samuel-duffield1@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:49:47 +0100 Subject: [PATCH 47/81] Add support for the Challenger rp2040 lora (#7826) * Firmware Built... awaiting parts for test * Add board_level key/value as per suggestion from vidplace7 * Trunk formatting applied --- .../challenger_2040_lora/pins_arduino.h | 79 +++++++++++++++++++ .../challenger_2040_lora/platformio.ini | 16 ++++ .../rp2040/challenger_2040_lora/variant.h | 39 +++++++++ 3 files changed, 134 insertions(+) create mode 100644 variants/rp2040/challenger_2040_lora/pins_arduino.h create mode 100644 variants/rp2040/challenger_2040_lora/platformio.ini create mode 100644 variants/rp2040/challenger_2040_lora/variant.h diff --git a/variants/rp2040/challenger_2040_lora/pins_arduino.h b/variants/rp2040/challenger_2040_lora/pins_arduino.h new file mode 100644 index 000000000..ac472c07e --- /dev/null +++ b/variants/rp2040/challenger_2040_lora/pins_arduino.h @@ -0,0 +1,79 @@ +#pragma once + +#define PINS_COUNT (25u) +#define NUM_DIGITAL_PINS (25u) +#define NUM_ANALOG_INPUTS (4u) +#define NUM_ANALOG_OUTPUTS (0u) +#define ADC_RESOLUTION (12u) + +// LEDs +#define PIN_LED (24u) + +// Serial +#define PIN_SERIAL1_TX (16u) +#define PIN_SERIAL1_RX (17u) + +// SPI +#define PIN_SPI0_MISO (20u) +#define PIN_SPI0_MOSI (23u) +#define PIN_SPI0_SCK (22u) +#define PIN_SPI0_SS (21u) + +// Connected to LoRa module +#define PIN_SPI1_MISO (12u) +#define PIN_SPI1_MOSI (11u) +#define PIN_SPI1_SCK (10u) +#define PIN_SPI1_SS (9u) +#define RFM95W_SS (9u) +#define RFM95W_DIO0 (14u) +#define RFM95W_DIO1 (15u) +#define RFM95W_DIO2 (18u) +#define RFM95W_RST (13u) +#define RFM95W_SPI SPI1 + +// Wire +#define PIN_WIRE0_SDA (0u) +#define PIN_WIRE0_SCL (1u) + +// Not pinned out +#define PIN_WIRE1_SDA (31u) +#define PIN_WIRE1_SCL (31u) +#define PIN_SERIAL2_RX (31u) +#define PIN_SERIAL2_TX (31u) + +#define SERIAL_HOWMANY (1u) +#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); +static const uint8_t D3 = (23u); +static const uint8_t D4 = (22u); +static const uint8_t D5 = (2u); +static const uint8_t D6 = (3u); +static const uint8_t D7 = (0u); +static const uint8_t D8 = (1u); +static const uint8_t D9 = (4u); +static const uint8_t D10 = (5u); +static const uint8_t D11 = (6u); +static const uint8_t D12 = (7u); +static const uint8_t D13 = (8u); +static const uint8_t D14 = (13u); +static const uint8_t D15 = (14u); +static const uint8_t D16 = (15u); +static const uint8_t D17 = (18u); +static const uint8_t D18 = (24u); + +static const uint8_t A0 = (26u); +static const uint8_t A1 = (27u); +static const uint8_t A2 = (28u); +static const uint8_t A3 = (29u); +static const uint8_t A4 = (19u); +static const uint8_t A5 = (21u); + +#ifndef SS +#define SS PIN_SPI1_SS +#endif \ No newline at end of file diff --git a/variants/rp2040/challenger_2040_lora/platformio.ini b/variants/rp2040/challenger_2040_lora/platformio.ini new file mode 100644 index 000000000..4a709d650 --- /dev/null +++ b/variants/rp2040/challenger_2040_lora/platformio.ini @@ -0,0 +1,16 @@ +[env:challenger_2040_lora] +extends = rp2040_base +board = challenger_2040_lora +board_level = extra +upload_protocol = picotool +# add our variants files to the include and src paths +build_flags = + ${rp2040_base.build_flags} + -D PRIVATE_HW + -I variants/rp2040/challenger_2040_lora + -D DEBUG_RP2040_PORT=Serial + -D HW_SPI1_DEVICE +lib_deps = + ${rp2040_base.lib_deps} +debug_build_flags = ${rp2040_base.build_flags} +debug_tool = cmsis-dap ; for e.g. Picotool diff --git a/variants/rp2040/challenger_2040_lora/variant.h b/variants/rp2040/challenger_2040_lora/variant.h new file mode 100644 index 000000000..552f90720 --- /dev/null +++ b/variants/rp2040/challenger_2040_lora/variant.h @@ -0,0 +1,39 @@ +// Define SS for compatibility with libraries expecting a default SPI chip select pin + +#define ARDUINO_ARCH_AVR + +#define EXT_NOTIFY_OUT 0xFFFFFFFF +#define BUTTON_PIN 0xFFFFFFFF + +#define LED_PIN PIN_LED + +#define USE_RF95 // RFM95/SX127x + +#undef LORA_SCK +#undef LORA_MISO +#undef LORA_MOSI +#undef LORA_CS + +// https://gitlab.com/invectorlabs/hw/challenger_rp2040_lora +#define LORA_SCK 10 // Clock +#define LORA_CS 9 // Chip Select +#define LORA_MOSI 11 // Serial Data Out +#define LORA_MISO 12 // Serial Data In + +#define LORA_RESET 13 // Reset + +#define LORA_DIO0 14 // DIO0 +#define LORA_DIO1 15 // DIO1 +#define LORA_DIO2 18 // DIO2 +#define LORA_DIO3 0xFFFFFFFF // Not connected +#define LORA_DIO4 0xFFFFFFFF // Not connected +#define LORA_DIO5 0xFFFFFFFF // Not connected + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +// #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file From 89de4991985b7d8f596ce0a5e3eca3323b80f825 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 07:32:59 -0500 Subject: [PATCH 48/81] Update protobufs (#7855) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 34f0c8115..07d6573e1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 34f0c8115d95f9f4be6d600095428a03833ac98e +Subproject commit 07d6573e1065344e80845de704885f011e515233 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index ce3722aa7..2a4e77870 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -272,6 +272,8 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_HELTEC_MESH_SOLAR = 108, /* Lilygo T-Echo Lite */ meshtastic_HardwareModel_T_ECHO_LITE = 109, + /* New Heltec LoRA32 with ESP32-S3 CPU */ + meshtastic_HardwareModel_HELTEC_V4 = 110, /* ------------------------------------------------------------------------------------------------------------------------------------------ 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. ------------------------------------------------------------------------------------------------------------------------------------------ */ From 12687a10739cb7017701964e68d784e6e0b6a941 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:53:21 -0500 Subject: [PATCH 49/81] chore(deps): update actions/github-script action to v8 (#7858) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pr_enforce_labels.yml | 2 +- .github/workflows/pr_tests.yml | 2 +- .github/workflows/trunk_format_pr.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr_enforce_labels.yml b/.github/workflows/pr_enforce_labels.yml index 93114e2c7..5fca90961 100644 --- a/.github/workflows/pr_enforce_labels.yml +++ b/.github/workflows/pr_enforce_labels.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Check for PR labels - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const labels = context.payload.pull_request.labels.map(label => label.name); diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index 786feeced..4e285852d 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -177,7 +177,7 @@ jobs: - name: Comment test results on PR if: github.event_name == 'pull_request' && needs.native-tests.result != 'skipped' - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const fs = require('fs'); diff --git a/.github/workflows/trunk_format_pr.yml b/.github/workflows/trunk_format_pr.yml index 2d191fc44..51082fc5f 100644 --- a/.github/workflows/trunk_format_pr.yml +++ b/.github/workflows/trunk_format_pr.yml @@ -39,7 +39,7 @@ jobs: git push - name: Comment on PR - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From 7fb96ce2bab8f2d960407ce18e11360a5e45367e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 12:53:46 -0500 Subject: [PATCH 50/81] chore(deps): update meshtastic/device-ui digest to a04bc94 (#7857) 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 61880c709..c58b14db1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -118,7 +118,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/10f02441ec7dcd099c4c5165c709afc3e0e3cb88.zip + https://github.com/meshtastic/device-ui/archive/a04bc94b45dacdabf3ae1832d4591390e35fc61f.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 68f07c5f9dc4e59868541eeabdf2dc928f892fa8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 4 Sep 2025 18:39:02 -0500 Subject: [PATCH 51/81] Board extras --- variants/esp32/heltec_wireless_bridge/platformio.ini | 1 + variants/esp32/trackerd/platformio.ini | 1 + variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini | 1 + variants/nrf52840/meshlink/platformio.ini | 1 + variants/nrf52840/meshlink_eink/platformio.ini | 1 + variants/rp2040/ec_catsniffer/platformio.ini | 1 + 6 files changed, 6 insertions(+) diff --git a/variants/esp32/heltec_wireless_bridge/platformio.ini b/variants/esp32/heltec_wireless_bridge/platformio.ini index 60e686f9e..93c3e3394 100644 --- a/variants/esp32/heltec_wireless_bridge/platformio.ini +++ b/variants/esp32/heltec_wireless_bridge/platformio.ini @@ -1,6 +1,7 @@ [env:heltec-wireless-bridge] ;build_type = debug ; to make it possible to step through our jtag debugger extends = esp32_base +board_level = extra board = heltec_wifi_lora_32 build_flags = ${esp32_base.build_flags} diff --git a/variants/esp32/trackerd/platformio.ini b/variants/esp32/trackerd/platformio.ini index 3c2726a3c..00c14fad2 100644 --- a/variants/esp32/trackerd/platformio.ini +++ b/variants/esp32/trackerd/platformio.ini @@ -1,5 +1,6 @@ [env:trackerd] extends = esp32_base +board_level = extra board = pico32 board_build.f_flash = 80000000L diff --git a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini index 72ac6320d..5c1047aae 100644 --- a/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini +++ b/variants/nrf52840/gat562_mesh_trial_tracker/platformio.ini @@ -1,6 +1,7 @@ ; The very slick RAK wireless RAK 4631 / 4630 board - Unified firmware for 5005/19003, with or without OLED RAK 1921 [env:gat562_mesh_trial_tracker] extends = nrf52840_base +board_level = extra board = gat562_mesh_trial_tracker board_check = true build_flags = ${nrf52840_base.build_flags} diff --git a/variants/nrf52840/meshlink/platformio.ini b/variants/nrf52840/meshlink/platformio.ini index 8216a704a..466362242 100644 --- a/variants/nrf52840/meshlink/platformio.ini +++ b/variants/nrf52840/meshlink/platformio.ini @@ -4,6 +4,7 @@ [env:meshlink] extends = nrf52840_base board = meshlink +board_level = extra ;board_check = true build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/meshlink diff --git a/variants/nrf52840/meshlink_eink/platformio.ini b/variants/nrf52840/meshlink_eink/platformio.ini index a48a9e695..af5a0040e 100644 --- a/variants/nrf52840/meshlink_eink/platformio.ini +++ b/variants/nrf52840/meshlink_eink/platformio.ini @@ -4,6 +4,7 @@ [env:meshlink_eink] extends = nrf52840_base board = meshlink +board_level = extra ;board_check = true build_flags = ${nrf52840_base.build_flags} -I variants/nrf52840/meshlink_eink diff --git a/variants/rp2040/ec_catsniffer/platformio.ini b/variants/rp2040/ec_catsniffer/platformio.ini index acf19d757..b70eff6d7 100644 --- a/variants/rp2040/ec_catsniffer/platformio.ini +++ b/variants/rp2040/ec_catsniffer/platformio.ini @@ -1,6 +1,7 @@ [env:catsniffer] extends = rp2040_base board = rpipico +board_level = extra upload_protocol = picotool build_flags = ${rp2040_base.build_flags} From 64cd62d6af914e28bfc45f0da268f6fccaaeb0be Mon Sep 17 00:00:00 2001 From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:33:02 -0400 Subject: [PATCH 52/81] Added Last Coordinate counter to Position screen (#7865) Adding a counter to show the last time a GPS coordinate was detected to ensure the user is aware how long since the coordinate updated or to identify any errors. --- src/GPSStatus.h | 9 ++++++ src/graphics/draw/UIRenderer.cpp | 53 +++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/GPSStatus.h b/src/GPSStatus.h index 4b7997935..a1a9f2c56 100644 --- a/src/GPSStatus.h +++ b/src/GPSStatus.h @@ -22,6 +22,9 @@ class GPSStatus : public Status meshtastic_Position p = meshtastic_Position_init_default; + /// Time of last valid GPS fix (millis since boot) + uint32_t lastFixMillis = 0; + public: GPSStatus() { statusType = STATUS_TYPE_GPS; } @@ -83,6 +86,9 @@ class GPSStatus : public Status uint32_t getNumSatellites() const { return p.sats_in_view; } + /// Return millis() when the last GPS fix occurred (0 = never) + uint32_t getLastFixMillis() const { return lastFixMillis; } + bool matches(const GPSStatus *newStatus) const { #ifdef GPS_DEBUG @@ -114,6 +120,9 @@ class GPSStatus : public Status if (isDirty) { if (hasLock) { + // Record time of last valid GPS fix + lastFixMillis = millis(); + // In debug logs, identify position by @timestamp:stage (stage 3 = notify) LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp, p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5, diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 049722df8..4ca981671 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -922,15 +922,52 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU // If GPS is off, no need to display these parts if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) { + /* MUST BE MOVED TO CLOCK SCREEN + // === Second Row: Date === + uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); + char datetimeStr[25]; + bool showTime = false; // set to true for full datetime + UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime); + char fullLine[40]; + snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr); + display->drawString(0, getTextPositions(display)[line++], fullLine); + */ - // === Second Row: Date === - uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); - char datetimeStr[25]; - bool showTime = false; // set to true for full datetime - UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime); - char fullLine[40]; - snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr); - display->drawString(0, getTextPositions(display)[line++], fullLine); + // === Second Row: Last GPS Fix === + if (gpsStatus->getLastFixMillis() > 0) { + uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix + uint32_t days = delta / 86400; + uint32_t hours = (delta % 86400) / 3600; + uint32_t mins = (delta % 3600) / 60; + uint32_t secs = delta % 60; + + char buf[32]; +#if defined(USE_EINK) + // E-Ink: skip seconds, show only days/hours/mins + if (days > 0) { + snprintf(buf, sizeof(buf), " Last: %ud %uh", days, hours); + } else if (hours > 0) { + snprintf(buf, sizeof(buf), " Last: %uh %um", hours, mins); + } else { + snprintf(buf, sizeof(buf), " Last: %um", mins); + } +#else + // Non E-Ink: include seconds where useful + if (days > 0) { + snprintf(buf, sizeof(buf), " Last: %ud %uh", days, hours); + } else if (hours > 0) { + snprintf(buf, sizeof(buf), " Last: %uh %um", hours, mins); + } else if (mins > 0) { + snprintf(buf, sizeof(buf), " Last: %um %us", mins, secs); + } else { + snprintf(buf, sizeof(buf), " Last: %us", secs); + } +#endif + + display->drawString(0, getTextPositions(display)[line++], buf); + } else { + display->drawString(0, getTextPositions(display)[line++], " Last: ?"); + } // === Third Row: Latitude === char latStr[32]; From f825e61b8907d751ae4b08d9b8d24d45fc55fcbd Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Fri, 5 Sep 2025 17:29:53 +0800 Subject: [PATCH 53/81] Add a new GPS model CM121. (#7852) * Add a new GPS model CM121. * Add CM121 to Unicore. * Trunk fixes, remove unneded NMEA lines --------- Co-authored-by: Tom Fifield --- src/gps/GPS.cpp | 18 ++++++++++++++++-- src/gps/GPS.h | 3 ++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index b23109268..b2904f2de 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -808,6 +808,14 @@ bool GPS::setup() } else { LOG_INFO("GNSS module configuration saved!"); } + } else if (gnssModel == GNSS_MODEL_CM121) { + // only ask for RMC and GGA + // enable GGA + _serial_gps->write("$CFGMSG,0,0,1,1*1B\r\n"); + delay(250); + // enable RMC + _serial_gps->write("$CFGMSG,0,4,1,1*1F\r\n"); + delay(250); } didSerialInit = true; } @@ -1240,9 +1248,15 @@ GnssModel_t GPS::probe(int serialSpeed) _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n"); _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); delay(20); + // Close NMEA sequences on CM121 + _serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n"); + _serial_gps->write("$CFGMSG,0,2,0,1*18\r\n"); + _serial_gps->write("$CFGMSG,0,3,0,1*19\r\n"); + delay(20); - // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A - std::vector unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}}; + // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121 + std::vector unicore = { + {"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}}; PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500); std::vector atgm = { diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 177cfe74b..1233003c8 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -31,7 +31,8 @@ typedef enum { GNSS_MODEL_MTK_PA1616S, GNSS_MODEL_AG3335, GNSS_MODEL_AG3352, - GNSS_MODEL_LS20031 + GNSS_MODEL_LS20031, + GNSS_MODEL_CM121 } GnssModel_t; typedef enum { From 3df3c876cca14dbc5f5b1b91d3831f45c7e79326 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 5 Sep 2025 06:22:21 -0500 Subject: [PATCH 54/81] TFTDisplay destructor --- src/graphics/TFTDisplay.cpp | 9 +++++++++ src/graphics/TFTDisplay.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index b1814005e..37ea9b94a 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -1128,6 +1128,15 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g #endif } +TFTDisplay::~TFTDisplay() +{ + // Clean up allocated line pixel buffer to prevent memory leak + if (linePixelBuffer != nullptr) { + free(linePixelBuffer); + linePixelBuffer = nullptr; + } +} + // Write the buffer to the display memory void TFTDisplay::display(bool fromBlank) { diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h index 27672ad29..a64922d23 100644 --- a/src/graphics/TFTDisplay.h +++ b/src/graphics/TFTDisplay.h @@ -20,6 +20,9 @@ class TFTDisplay : public OLEDDisplay */ TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C); + // Destructor to clean up allocated memory + ~TFTDisplay(); + // Write the buffer to the display memory virtual void display() override { display(false); }; virtual void display(bool fromBlank); From bf51c38975a6bae45ab3d6cb0588f48834d3ddc0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 5 Sep 2025 07:18:03 -0500 Subject: [PATCH 55/81] Don't add heap allocations while debugging the heap --- src/Power.cpp | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index a123fe984..06c6a9089 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -833,16 +833,25 @@ void Power::readPowerStatus() newStatus.notifyObservers(&powerStatus2); #ifdef DEBUG_HEAP if (lastheap != memGet.getFreeHeap()) { - std::string threadlist = "Threads running:"; + // Use stack-allocated buffer to avoid heap allocations in monitoring code + char threadlist[256] = "Threads running:"; + int threadlistLen = strlen(threadlist); int running = 0; for (int i = 0; i < MAX_THREADS; i++) { auto thread = concurrency::mainController.get(i); if ((thread != nullptr) && (thread->enabled)) { - threadlist += vformat(" %s", thread->ThreadName.c_str()); + // Use snprintf to safely append to stack buffer without heap allocation + int remaining = sizeof(threadlist) - threadlistLen - 1; + if (remaining > 0) { + int written = snprintf(threadlist + threadlistLen, remaining, " %s", thread->ThreadName.c_str()); + if (written > 0 && written < remaining) { + threadlistLen += written; + } + } running++; } } - LOG_DEBUG(threadlist.c_str()); + LOG_DEBUG(threadlist); LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(), memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false)); lastheap = memGet.getFreeHeap(); @@ -856,15 +865,19 @@ void Power::readPowerStatus() sprintf(mac, "!%02x%02x%02x%02x", dmac[2], dmac[3], dmac[4], dmac[5]); auto newHeap = memGet.getFreeHeap(); - std::string heapTopic = - (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/heap/") + std::string(mac); - std::string heapString = std::to_string(newHeap); - mqtt->pubSub.publish(heapTopic.c_str(), heapString.c_str(), false); + // Use stack-allocated buffers to avoid heap allocations in monitoring code + char heapTopic[128]; + snprintf(heapTopic, sizeof(heapTopic), "%s/2/heap/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac); + char heapString[16]; + snprintf(heapString, sizeof(heapString), "%u", newHeap); + mqtt->pubSub.publish(heapTopic, heapString, false); + auto wifiRSSI = WiFi.RSSI(); - std::string wifiTopic = - (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh") + std::string("/2/wifi/") + std::string(mac); - std::string wifiString = std::to_string(wifiRSSI); - mqtt->pubSub.publish(wifiTopic.c_str(), wifiString.c_str(), false); + char wifiTopic[128]; + snprintf(wifiTopic, sizeof(wifiTopic), "%s/2/wifi/%s", (*moduleConfig.mqtt.root ? moduleConfig.mqtt.root : "msh"), mac); + char wifiString[16]; + snprintf(wifiString, sizeof(wifiString), "%d", wifiRSSI); + mqtt->pubSub.publish(wifiTopic, wifiString, false); } #endif From 8356ad97e440468aa562afb3f16f3d7b748543cc Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 5 Sep 2025 07:18:29 -0500 Subject: [PATCH 56/81] Cleanup file list --- src/mesh/http/ContentHandler.cpp | 50 ++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 42ebb8417..74953d8fc 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -342,6 +342,11 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res) res->print(value->Stringify().c_str()); delete value; + + // Clean up the fileList to prevent memory leak + for (auto *val : fileList) { + delete val; + } } void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) @@ -610,33 +615,38 @@ void handleReport(HTTPRequest *req, HTTPResponse *res) res->println("
");
     }
 
+    // Helper lambda to create JSON array and clean up memory properly
+    auto createJSONArrayFromLog = [](uint32_t *logArray, int count) -> JSONValue * {
+        JSONArray tempArray;
+        for (int i = 0; i < count; i++) {
+            tempArray.push_back(new JSONValue((int)logArray[i]));
+        }
+        JSONValue *result = new JSONValue(tempArray);
+        // Clean up original array to prevent memory leak
+        for (auto *val : tempArray) {
+            delete val;
+        }
+        return result;
+    };
+
     // data->airtime->tx_log
-    JSONArray txLogValues;
     uint32_t *logArray;
     logArray = airTime->airtimeReport(TX_LOG);
-    for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
-        txLogValues.push_back(new JSONValue((int)logArray[i]));
-    }
+    JSONValue *txLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
 
     // data->airtime->rx_log
-    JSONArray rxLogValues;
     logArray = airTime->airtimeReport(RX_LOG);
-    for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
-        rxLogValues.push_back(new JSONValue((int)logArray[i]));
-    }
+    JSONValue *rxLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
 
     // data->airtime->rx_all_log
-    JSONArray rxAllLogValues;
     logArray = airTime->airtimeReport(RX_ALL_LOG);
-    for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
-        rxAllLogValues.push_back(new JSONValue((int)logArray[i]));
-    }
+    JSONValue *rxAllLogJsonValue = createJSONArrayFromLog(logArray, airTime->getPeriodsToLog());
 
     // data->airtime
     JSONObject jsonObjAirtime;
-    jsonObjAirtime["tx_log"] = new JSONValue(txLogValues);
-    jsonObjAirtime["rx_log"] = new JSONValue(rxLogValues);
-    jsonObjAirtime["rx_all_log"] = new JSONValue(rxAllLogValues);
+    jsonObjAirtime["tx_log"] = txLogJsonValue;
+    jsonObjAirtime["rx_log"] = rxLogJsonValue;
+    jsonObjAirtime["rx_all_log"] = rxAllLogJsonValue;
     jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent());
     jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent());
     jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot()));
@@ -765,6 +775,11 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
     JSONValue *value = new JSONValue(jsonObjOuter);
     res->print(value->Stringify().c_str());
     delete value;
+
+    // Clean up the nodesArray to prevent memory leak
+    for (auto *val : nodesArray) {
+        delete val;
+    }
 }
 
 /*
@@ -955,5 +970,10 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
     JSONValue *value = new JSONValue(jsonObjOuter);
     res->print(value->Stringify().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

From ec9f3fa6eace6cbae819dd7456bfda96a17893b8 Mon Sep 17 00:00:00 2001
From: Manuel <71137295+mverch67@users.noreply.github.com>
Date: Fri, 5 Sep 2025 14:42:51 +0200
Subject: [PATCH 57/81] T-Lora Pager: fix keyboard and improve rotary wheel
 haptic (#7869)

* update RotaryEncoder: use interrupts

* increase rotary encoder processing interval

* remove disabling peripherals during LS
---
 src/input/RotaryEncoderImpl.cpp             |  2 +-
 src/sleep.cpp                               | 17 -----------------
 variants/esp32s3/tlora-pager/platformio.ini |  2 +-
 3 files changed, 2 insertions(+), 19 deletions(-)

diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp
index d3fcbbf9d..e00c1cc6f 100644
--- a/src/input/RotaryEncoderImpl.cpp
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -70,7 +70,7 @@ int32_t RotaryEncoderImpl::runOnce()
         this->notifyObservers(&e);
     }
 
-    return 20;
+    return 10;
 }
 
 #endif
\ No newline at end of file
diff --git a/src/sleep.cpp b/src/sleep.cpp
index bff318900..83597e349 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -431,15 +431,6 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
         gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq
 #endif
 
-#ifdef T_LORA_PAGER
-    LOG_DEBUG("power down XL9555 io");
-    io.digitalWrite(EXPANDS_DRV_EN, LOW);
-    io.digitalWrite(EXPANDS_AMP_EN, LOW);
-    io.digitalWrite(EXPANDS_KB_EN, LOW);
-    io.digitalWrite(EXPANDS_SD_EN, LOW);
-    io.digitalWrite(EXPANDS_GPIO_EN, LOW);
-#endif
-
     auto res = esp_sleep_enable_gpio_wakeup();
     if (res != ESP_OK) {
         LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res);
@@ -480,14 +471,6 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
         gpio_wakeup_disable((gpio_num_t)RF95_IRQ);
     }
 #endif
-#ifdef T_LORA_PAGER
-    LOG_DEBUG("power up XL9555 io");
-    io.digitalWrite(EXPANDS_DRV_EN, HIGH);
-    io.digitalWrite(EXPANDS_AMP_EN, HIGH);
-    io.digitalWrite(EXPANDS_KB_EN, HIGH);
-    io.digitalWrite(EXPANDS_SD_EN, HIGH);
-    io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
-#endif
 
     esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
     notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here
diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini
index b16e516a7..312d46259 100644
--- a/variants/esp32s3/tlora-pager/platformio.ini
+++ b/variants/esp32s3/tlora-pager/platformio.ini
@@ -26,7 +26,7 @@ lib_deps = ${esp32s3_base.lib_deps}
   lewisxhe/SensorLib@0.3.1
   https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip
   https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
-  https://github.com/mverch67/RotaryEncoder
+  https://github.com/mverch67/RotaryEncoder/archive/25a59d5745a6645536f921427d80b08e78f886d4.zip
 
 [env:tlora-pager-tft]
 board_level = extra

From 4d6fe936ae84307ba820ff25ea5fc8693ea8d17c Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Fri, 5 Sep 2025 18:01:25 +0200
Subject: [PATCH 58/81] Only stop retransmissions when receiving implicit ACK
 over LoRa (#7872)

* Only stop retransmissions when receiving implicit ACK over LoRa

* trunk fmt
---
 src/mesh/NextHopRouter.cpp                    | 7 +++++--
 src/mesh/ReliableRouter.cpp                   | 5 ++++-
 variants/esp32s3/rak_wismesh_tap_v2/variant.h | 2 +-
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 860250f75..794b25aa6 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -34,8 +34,11 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
     bool weWereNextHop = false;
     if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
         printPacket("Ignore dupe incoming msg", p);
-        rxDupe++;
-        stopRetransmission(p->from, p->id);
+
+        if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
+            rxDupe++;
+            stopRetransmission(p->from, p->id);
+        }
 
         // If it was a fallback to flooding, try to relay again
         if (wasFallback) {
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index 6e5c6231b..e9ceeaef1 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -58,7 +58,10 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
             // marked as wantAck
             sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, old->packet->channel);
 
-            stopRetransmission(key);
+            // Only stop retransmissions if the rebroadcast came via LoRa
+            if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
+                stopRetransmission(key);
+            }
         } else {
             LOG_DEBUG("Didn't find pending packet");
         }
diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h
index 8468c557e..2fc056557 100644
--- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h
+++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h
@@ -61,7 +61,7 @@
 
 #define HAS_SDCARD 1
 #define SDCARD_USE_SPI1 1
-#define SDCARD_CS  2
+#define SDCARD_CS 2
 
 #define SPI_FREQUENCY 40000000
 #define SPI_READ_FREQUENCY 16000000

From a25bfd264c4ca0c18e2e3e1fd1f96c233d7dfae4 Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Fri, 5 Sep 2025 18:01:25 +0200
Subject: [PATCH 59/81] Only stop retransmissions when receiving implicit ACK
 over LoRa (#7872)

* Only stop retransmissions when receiving implicit ACK over LoRa

* trunk fmt
---
 src/mesh/NextHopRouter.cpp                    | 7 +++++--
 src/mesh/ReliableRouter.cpp                   | 5 ++++-
 variants/esp32s3/rak_wismesh_tap_v2/variant.h | 2 +-
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 860250f75..794b25aa6 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -34,8 +34,11 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
     bool weWereNextHop = false;
     if (wasSeenRecently(p, true, &wasFallback, &weWereNextHop)) { // Note: this will also add a recent packet record
         printPacket("Ignore dupe incoming msg", p);
-        rxDupe++;
-        stopRetransmission(p->from, p->id);
+
+        if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
+            rxDupe++;
+            stopRetransmission(p->from, p->id);
+        }
 
         // If it was a fallback to flooding, try to relay again
         if (wasFallback) {
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index 6e5c6231b..e9ceeaef1 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -58,7 +58,10 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
             // marked as wantAck
             sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, old->packet->channel);
 
-            stopRetransmission(key);
+            // Only stop retransmissions if the rebroadcast came via LoRa
+            if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA) {
+                stopRetransmission(key);
+            }
         } else {
             LOG_DEBUG("Didn't find pending packet");
         }
diff --git a/variants/esp32s3/rak_wismesh_tap_v2/variant.h b/variants/esp32s3/rak_wismesh_tap_v2/variant.h
index 8468c557e..2fc056557 100644
--- a/variants/esp32s3/rak_wismesh_tap_v2/variant.h
+++ b/variants/esp32s3/rak_wismesh_tap_v2/variant.h
@@ -61,7 +61,7 @@
 
 #define HAS_SDCARD 1
 #define SDCARD_USE_SPI1 1
-#define SDCARD_CS  2
+#define SDCARD_CS 2
 
 #define SPI_FREQUENCY 40000000
 #define SPI_READ_FREQUENCY 16000000

From 50a5b36498efcc7a8ec2a6ad909e961a841192f6 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Fri, 5 Sep 2025 20:44:32 -0500
Subject: [PATCH 60/81] BaseUI Updates (#7787)

* Account for low resolution wide screen OLEDs

* Allow picking of Device Role and new Display Formatter for Device Role

* Add remainder of client roles to display formatter

* Don't update the role unless you pick a value

* Mascots are fun

* Fix warnings during compile time

* Improve some menus

* Mascots need to work everywhere

* Update Chirpy image

* Fix Trunk

* Update protobufs

* Add date to Clock screen

* Analog clocks love dates too

* Finalize date moves for analog clock
---
 protobufs                           |  2 +-
 src/DisplayFormatters.cpp           | 42 ++++++++++++++++
 src/DisplayFormatters.h             |  3 ++
 src/graphics/Screen.cpp             | 10 ++++
 src/graphics/Screen.h               |  2 +
 src/graphics/SharedUIDisplay.cpp    |  4 ++
 src/graphics/draw/ClockRenderer.cpp | 25 ++++++++++
 src/graphics/draw/DebugRenderer.cpp | 48 +++++++++++++++---
 src/graphics/draw/DebugRenderer.h   |  3 ++
 src/graphics/draw/MenuHandler.cpp   | 77 +++++++++++++++++++++++++----
 src/graphics/draw/MenuHandler.h     |  3 ++
 src/graphics/draw/UIRenderer.cpp    | 29 ++++-------
 src/graphics/images.h               | 72 +++++++++++++++++++++++++++
 13 files changed, 284 insertions(+), 36 deletions(-)

diff --git a/protobufs b/protobufs
index 34f0c8115..4c4427c4a 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 34f0c8115d95f9f4be6d600095428a03833ac98e
+Subproject commit 4c4427c4a73c86fed7dc8632188bb8be95349d81
diff --git a/src/DisplayFormatters.cpp b/src/DisplayFormatters.cpp
index d367aa661..b2749806c 100644
--- a/src/DisplayFormatters.cpp
+++ b/src/DisplayFormatters.cpp
@@ -38,4 +38,46 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
         return useShortName ? "Custom" : "Invalid";
         break;
     }
+}
+
+const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role role)
+{
+    switch (role) {
+    case meshtastic_Config_DeviceConfig_Role_CLIENT:
+        return "Client";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE:
+        return "Client Mute";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN:
+        return "Client Hidden";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND:
+        return "Lost and Found";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_TRACKER:
+        return "Tracker";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_SENSOR:
+        return "Sensor";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_TAK:
+        return "TAK";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_TAK_TRACKER:
+        return "TAK Tracker";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_ROUTER:
+        return "Router";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
+        return "Router Late";
+        break;
+    case meshtastic_Config_DeviceConfig_Role_REPEATER:
+        return "Repeater";
+        break;
+    default:
+        return "Unknown";
+        break;
+    }
 }
\ No newline at end of file
diff --git a/src/DisplayFormatters.h b/src/DisplayFormatters.h
index 2d7a3e8db..ad193e966 100644
--- a/src/DisplayFormatters.h
+++ b/src/DisplayFormatters.h
@@ -6,4 +6,7 @@ class DisplayFormatters
   public:
     static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
                                                  bool usePreset);
+
+  public:
+    static const char *getDeviceRole(meshtastic_Config_DeviceConfig_Role role);
 };
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 3e45bed45..eb8093947 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -1018,6 +1018,11 @@ void Screen::setFrames(FrameFocus focus)
         indicatorIcons.push_back(digital_icon_clock);
     }
 #endif
+    if (!hiddenFrames.chirpy) {
+        fsi.positions.chirpy = numframes;
+        normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
+        indicatorIcons.push_back(small_chirpy);
+    }
 
 #if HAS_WIFI && !defined(ARCH_PORTDUINO)
     if (!hiddenFrames.wifi && isWifiAvailable()) {
@@ -1186,6 +1191,9 @@ void Screen::toggleFrameVisibility(const std::string &frameName)
     if (frameName == "show_favorites") {
         hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
     }
+    if (frameName == "chirpy") {
+        hiddenFrames.chirpy = !hiddenFrames.chirpy;
+    }
 }
 
 bool Screen::isFrameHidden(const std::string &frameName) const
@@ -1214,6 +1222,8 @@ bool Screen::isFrameHidden(const std::string &frameName) const
         return hiddenFrames.clock;
     if (frameName == "show_favorites")
         return hiddenFrames.show_favorites;
+    if (frameName == "chirpy")
+        return hiddenFrames.chirpy;
 
     return false;
 }
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 8c13bcf9a..55ce20052 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -669,6 +669,7 @@ class Screen : public concurrency::OSThread
             uint8_t nodelist_distance = 255;
             uint8_t nodelist_bearings = 255;
             uint8_t clock = 255;
+            uint8_t chirpy = 255;
             uint8_t firstFavorite = 255;
             uint8_t lastFavorite = 255;
             uint8_t lora = 255;
@@ -698,6 +699,7 @@ class Screen : public concurrency::OSThread
 #endif
         bool lora = false;
         bool show_favorites = false;
+        bool chirpy = true;
     } hiddenFrames;
 
     /// Try to start drawing ASAP
diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index b458e54e4..53fb8e993 100644
--- a/src/graphics/SharedUIDisplay.cpp
+++ b/src/graphics/SharedUIDisplay.cpp
@@ -16,6 +16,10 @@ void determineResolution(int16_t screenheight, int16_t screenwidth)
         isHighResolution = true;
     }
 
+    if (screenwidth > 128 && screenheight <= 64) {
+        isHighResolution = false;
+    }
+
     // Special case for Heltec Wireless Tracker v1.1
     if (screenwidth == 160 && screenheight == 80) {
         isHighResolution = false;
diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index 08466662c..d046bda6f 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -191,6 +191,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
     const char *titleStr = "";
     // === Header ===
     graphics::drawCommonHeader(display, x, y, titleStr, true);
+    int line = 0;
 
 #ifdef T_WATCH_S3
     if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
@@ -294,11 +295,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
         display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
                             isPM ? "pm" : "am");
     }
+
 #ifndef USE_EINK
     xOffset = (isHighResolution) ? 18 : 10;
     display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
                         secondString);
 #endif
+
+    // Display GPS derived date
+    char datetimeStr[25];
+    UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
+    char fullLine[40];
+    snprintf(fullLine, sizeof(fullLine), "%s", datetimeStr);
+    yOffset = (isHighResolution) ? 12 : 1;
+    display->drawString(startingHourMinuteTextX + timeStringWidth - display->getStringWidth(fullLine),
+                        getTextPositions(display)[line] + yOffset, fullLine);
 }
 
 void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
@@ -314,6 +325,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
     const char *titleStr = "";
     // === Header ===
     graphics::drawCommonHeader(display, x, y, titleStr, true);
+    int line = 0;
 
 #ifdef T_WATCH_S3
     if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
@@ -511,6 +523,19 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
         // draw second hand
         display->drawLine(centerX, centerY, secondX, secondY);
 #endif
+
+        display->setFont(FONT_SMALL);
+        // Display GPS derived date
+        char datetimeStr[25];
+        UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
+        char fullLine[40];
+        if (isHighResolution) {
+            snprintf(fullLine, sizeof(fullLine), "%s", datetimeStr);
+        } else {
+            snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
+        }
+        display->drawString(display->getWidth() - 1 - display->getStringWidth(fullLine), getTextPositions(display)[line],
+                            fullLine);
     }
 }
 
diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index 3e4030e0f..c93ef578c 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -391,18 +391,27 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     int nameX = (SCREEN_WIDTH - textWidth);
     display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
 
-    // === Second Row: Radio Preset ===
+    // === Second Row: Role ===
+    auto role = DisplayFormatters::getDeviceRole(config.device.role);
+    char device_role[25];
+    snprintf(device_role, sizeof(device_role), "Role: %s", role);
+    textWidth = display->getStringWidth(device_role);
+    nameX = (SCREEN_WIDTH - textWidth) / 2;
+    display->drawString(nameX, getTextPositions(display)[line++], device_role);
+
+    // === Third Row: Radio Preset ===
     auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
+
     char regionradiopreset[25];
     const char *region = myRegion ? myRegion->name : NULL;
     if (region != nullptr) {
-        snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
+        snprintf(regionradiopreset, sizeof(regionradiopreset), "Reg: %s/%s", region, mode);
     }
     textWidth = display->getStringWidth(regionradiopreset);
     nameX = (SCREEN_WIDTH - textWidth) / 2;
     display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
 
-    // === Third Row: Frequency / ChanNum ===
+    // === Fourth Row: Frequency / ChanNum ===
     char frequencyslot[35];
     char freqStr[16];
     float freq = RadioLibInterface::instance->getFreq();
@@ -420,7 +429,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     nameX = (SCREEN_WIDTH - textWidth) / 2;
     display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
 
-    // === Fourth Row: Channel Utilization ===
+    // === Fifth Row: Channel Utilization ===
     const char *chUtil = "ChUtil:";
     char chUtilPercentage[10];
     snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
@@ -437,7 +446,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
     int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
     int starting_position = centerofscreen - total_line_content_width;
 
-    display->drawString(starting_position, getTextPositions(display)[line++], chUtil);
+    display->drawString(starting_position, getTextPositions(display)[line], chUtil);
 
     // Force 56% or higher to show a full 100% bar, text would still show related percent.
     if (chutil_percent >= 61) {
@@ -474,7 +483,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
         display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
     }
 
-    display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4],
+    display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
                         chUtilPercentage);
 }
 
@@ -625,6 +634,33 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
         display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
     }
 }
+
+// ****************************
+// * Chirpy Screen      *
+// ****************************
+void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+{
+    display->clear();
+    display->setTextAlignment(TEXT_ALIGN_LEFT);
+    display->setFont(FONT_SMALL);
+    int line = 1;
+    int iconX = SCREEN_WIDTH - chirpy_width - (chirpy_width / 3);
+    int iconY = (SCREEN_HEIGHT - chirpy_height) / 2;
+    int textX_offset = 10;
+    if (isHighResolution) {
+        iconX = SCREEN_WIDTH - chirpy_width_hirez - (chirpy_width_hirez / 3);
+        iconY = (SCREEN_HEIGHT - chirpy_height_hirez) / 2;
+        textX_offset = textX_offset * 4;
+        display->drawXbm(iconX, iconY, chirpy_width_hirez, chirpy_height_hirez, chirpy_hirez);
+    } else {
+        display->drawXbm(iconX, iconY, chirpy_width, chirpy_height, chirpy);
+    }
+
+    int textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("Hello") / 2);
+    display->drawString(textX, getTextPositions(display)[line++], "Hello");
+    textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("World!") / 2);
+    display->drawString(textX, getTextPositions(display)[line++], "World!");
+}
 } // namespace DebugRenderer
 } // namespace graphics
 #endif
\ No newline at end of file
diff --git a/src/graphics/draw/DebugRenderer.h b/src/graphics/draw/DebugRenderer.h
index 3382e931d..563a6c1ce 100644
--- a/src/graphics/draw/DebugRenderer.h
+++ b/src/graphics/draw/DebugRenderer.h
@@ -33,6 +33,9 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
 
 // System screen display
 void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+
+// Chirpy screen display
+void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
 } // namespace DebugRenderer
 
 } // namespace graphics
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index e92a54751..dab3040f0 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -31,17 +31,19 @@ uint8_t test_count = 0;
 
 void menuHandler::loraMenu()
 {
-    static const char *optionsArray[] = {"Back", "Region Picker"};
-    enum optionsNumbers { Back = 0, lora_picker = 1 };
+    static const char *optionsArray[] = {"Back", "Region Picker", "Device Role"};
+    enum optionsNumbers { Back = 0, lora_picker = 1, device_role_picker = 2 };
     BannerOverlayOptions bannerOptions;
     bannerOptions.message = "LoRa Actions";
     bannerOptions.optionsArrayPtr = optionsArray;
-    bannerOptions.optionsCount = 2;
+    bannerOptions.optionsCount = 3;
     bannerOptions.bannerCallback = [](int selected) -> void {
         if (selected == Back) {
             // No action
         } else if (selected == lora_picker) {
             menuHandler::menuQueue = menuHandler::lora_picker;
+        } else if (selected == device_role_picker) {
+            menuHandler::menuQueue = menuHandler::device_role_picker;
         }
     };
     screen->showOverlayBanner(bannerOptions);
@@ -140,6 +142,40 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
     screen->showOverlayBanner(bannerOptions);
 }
 
+void menuHandler::DeviceRolePicker()
+{
+    static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"};
+    enum optionsNumbers {
+        Back = 0,
+        devicerole_client = 1,
+        devicerole_clientmute = 2,
+        devicerole_lostandfound = 3,
+        devicerole_tracker = 4
+    };
+    BannerOverlayOptions bannerOptions;
+    bannerOptions.message = "Device Role";
+    bannerOptions.optionsArrayPtr = optionsArray;
+    bannerOptions.optionsCount = 5;
+    bannerOptions.bannerCallback = [](int selected) -> void {
+        if (selected == Back) {
+            menuHandler::menuQueue = menuHandler::lora_Menu;
+            screen->runNow();
+            return;
+        } else if (selected == devicerole_client) {
+            config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
+        } else if (selected == devicerole_clientmute) {
+            config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE;
+        } else if (selected == devicerole_lostandfound) {
+            config.device.role = meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND;
+        } else if (selected == devicerole_tracker) {
+            config.device.role = meshtastic_Config_DeviceConfig_Role_TRACKER;
+        }
+        service->reloadConfig(SEGMENT_CONFIG);
+        rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
+    };
+    screen->showOverlayBanner(bannerOptions);
+}
+
 void menuHandler::TwelveHourPicker()
 {
     static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
@@ -968,16 +1004,33 @@ void menuHandler::traceRouteMenu()
 void menuHandler::testMenu()
 {
 
-    static const char *optionsArray[] = {"Back", "Number Picker"};
+    enum optionsNumbers { Back, NumberPicker, ShowChirpy };
+    static const char *optionsArray[4] = {"Back"};
+    static int optionsEnumArray[4] = {Back};
+    int options = 1;
+
+    optionsArray[options] = "Number Picker";
+    optionsEnumArray[options++] = NumberPicker;
+
+    optionsArray[options] = screen->isFrameHidden("chirpy") ? "Show Chirpy" : "Hide Chirpy";
+    optionsEnumArray[options++] = ShowChirpy;
+
     BannerOverlayOptions bannerOptions;
-    std::string message = "Test to Run?\n";
-    bannerOptions.message = message.c_str();
+    bannerOptions.message = "Hidden Test Menu";
     bannerOptions.optionsArrayPtr = optionsArray;
-    bannerOptions.optionsCount = 2;
+    bannerOptions.optionsCount = options;
+    bannerOptions.optionsEnumPtr = optionsEnumArray;
     bannerOptions.bannerCallback = [](int selected) -> void {
-        if (selected == 1) {
+        if (selected == NumberPicker) {
             menuQueue = number_test;
             screen->runNow();
+        } else if (selected == ShowChirpy) {
+            screen->toggleFrameVisibility("chirpy");
+            screen->setFrames(Screen::FOCUS_SYSTEM);
+
+        } else {
+            menuQueue = system_base_menu;
+            screen->runNow();
         }
     };
     screen->showOverlayBanner(bannerOptions);
@@ -1232,7 +1285,7 @@ void menuHandler::FrameToggles_menu()
     bannerOptions.optionsEnumPtr = optionsEnumArray;
     bannerOptions.InitialSelected = lastSelectedIndex; // Use index, not enum value
 
-    bannerOptions.bannerCallback = [optionsEnumArray, options](int selected) mutable -> void {
+    bannerOptions.bannerCallback = [options](int selected) mutable -> void {
         // Find the index of selected in optionsEnumArray
         int idx = 0;
         for (; idx < options; ++idx) {
@@ -1291,9 +1344,15 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
     switch (menuQueue) {
     case menu_none:
         break;
+    case lora_Menu:
+        loraMenu();
+        break;
     case lora_picker:
         LoraRegionPicker();
         break;
+    case device_role_picker:
+        DeviceRolePicker();
+        break;
     case no_timeout_lora_picker:
         LoraRegionPicker(0);
         break;
diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h
index 00df22d6c..2be8e58a6 100644
--- a/src/graphics/draw/MenuHandler.h
+++ b/src/graphics/draw/MenuHandler.h
@@ -9,7 +9,9 @@ class menuHandler
   public:
     enum screenMenus {
         menu_none,
+        lora_Menu,
         lora_picker,
+        device_role_picker,
         no_timeout_lora_picker,
         TZ_picker,
         twelve_hour_picker,
@@ -46,6 +48,7 @@ class menuHandler
     static void OnboardMessage();
     static void LoraRegionPicker(uint32_t duration = 30000);
     static void loraMenu();
+    static void DeviceRolePicker();
     static void handleMenuSwitch(OLEDDisplay *display);
     static void showConfirmationBanner(const char *message, std::function onConfirm);
     static void clockMenu();
diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 4ca981671..7a4a06ed6 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -922,17 +922,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
 
     // If GPS is off, no need to display these parts
     if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
-        /* MUST BE MOVED TO CLOCK SCREEN
-            // === Second Row: Date ===
-            uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
-            char datetimeStr[25];
-            bool showTime = false; // set to true for full datetime
-            UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
-            char fullLine[40];
-            snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
-            display->drawString(0, getTextPositions(display)[line++], fullLine);
-        */
-
         // === Second Row: Last GPS Fix ===
         if (gpsStatus->getLastFixMillis() > 0) {
             uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
@@ -954,37 +943,37 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
 #else
             // Non E-Ink: include seconds where useful
             if (days > 0) {
-                snprintf(buf, sizeof(buf), " Last: %ud %uh", days, hours);
+                snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
             } else if (hours > 0) {
-                snprintf(buf, sizeof(buf), " Last: %uh %um", hours, mins);
+                snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
             } else if (mins > 0) {
-                snprintf(buf, sizeof(buf), " Last: %um %us", mins, secs);
+                snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
             } else {
-                snprintf(buf, sizeof(buf), " Last: %us", secs);
+                snprintf(buf, sizeof(buf), "Last: %us", secs);
             }
 #endif
 
             display->drawString(0, getTextPositions(display)[line++], buf);
         } else {
-            display->drawString(0, getTextPositions(display)[line++], " Last: ?");
+            display->drawString(0, getTextPositions(display)[line++], "Last: ?");
         }
 
         // === Third Row: Latitude ===
         char latStr[32];
-        snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
+        snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
         display->drawString(x, getTextPositions(display)[line++], latStr);
 
         // === Fourth Row: Longitude ===
         char lonStr[32];
-        snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
+        snprintf(lonStr, sizeof(lonStr), "Lon: %.5f", geoCoord.getLongitude() * 1e-7);
         display->drawString(x, getTextPositions(display)[line++], lonStr);
 
         // === Fifth Row: Altitude ===
         char DisplayLineTwo[32] = {0};
         if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
-            snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
+            snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
         } else {
-            snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude());
+            snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0im", geoCoord.getAltitude());
         }
         display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
     }
diff --git a/src/graphics/images.h b/src/graphics/images.h
index e349cb6e0..168a9b716 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -288,5 +288,77 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
                                              0b00100100, 0b01000010, 0b01000010, 0b11111111};
 
+#define chirpy_width 38
+#define chirpy_height 50
+static unsigned char chirpy[] = {
+    0xfe, 0xff, 0xff, 0xff, 0xdf, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01,
+    0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01, 0x00,
+    0x00, 0x00, 0xe0, 0x81, 0xff, 0xff, 0x7f, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xcf, 0x7f,
+    0xfe, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc,
+    0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0,
+    0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1,
+    0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0xcf, 0x7f, 0xfe, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0x81, 0xff,
+    0xff, 0x7f, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xc3, 0x00, 0xe0, 0x01, 0x00, 0xc3,
+    0x00, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x30, 0x03, 0xe0, 0x01, 0xc0, 0x30, 0x03,
+    0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0,
+    0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01,
+    0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xdf};
+
+#define chirpy_width_hirez 76
+#define chirpy_height_hirez 100
+static unsigned char chirpy_hirez[] = {
+    0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x03,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00,
+    0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc,
+    0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03,
+    0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0,
+    0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
+    0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
+    0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff,
+    0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f,
+    0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0,
+    0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff,
+    0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00,
+    0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc,
+    0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03,
+    0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0,
+    0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
+    0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
+    0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff,
+    0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
+    0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
+    0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03,
+    0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
+    0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
+    0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0,
+    0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00,
+    0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f,
+    0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c,
+    0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00,
+    0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00,
+    0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc,
+    0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03,
+    0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00,
+    0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0,
+    0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3};
+
+#define chirpy_small_image_width 8
+#define chirpy_small_image_height 8
+static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
+
 #include "img/icon.xbm"
 static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
\ No newline at end of file

From d6df66410204b147a2b5cf7c35435c0b7ad2290c Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Fri, 5 Sep 2025 23:06:58 -0400
Subject: [PATCH 61/81] Phone GPS display on Position Screen for BaseUI (#7875)

* Phone GPS display on Position Screen

This is a PR to show when a phone shares GPS location with the node so you can reliably know what coordinate is being shared with the Mesh.
---
 src/graphics/draw/UIRenderer.cpp | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp
index 7a4a06ed6..e9da66712 100644
--- a/src/graphics/draw/UIRenderer.cpp
+++ b/src/graphics/draw/UIRenderer.cpp
@@ -879,7 +879,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
     config.display.heading_bold = false;
 
     const char *displayLine = ""; // Initialize to empty string by default
-    if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
+    meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
+
+    bool usePhoneGPS = (ourNode && nodeDB->hasValidPosition(ourNode) &&
+                        config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED);
+
+    if (usePhoneGPS) {
+        // Phone-provided GPS is active
+        displayLine = "Phone GPS";
+        int yOffset = (isHighResolution) ? 3 : 1;
+        if (isHighResolution) {
+            NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
+                                                     imgSatellite_height, imgSatellite, display);
+        } else {
+            display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
+                             imgSatellite);
+        }
+        int xOffset = (isHighResolution) ? 6 : 0;
+        display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
+    } else if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
+        // GPS disabled / not present
         if (config.position.fixed_position) {
             displayLine = "Fixed GPS";
         } else {
@@ -896,6 +915,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
         int xOffset = (isHighResolution) ? 6 : 0;
         display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
     } else {
+        // Onboard GPS
         UIRenderer::drawGps(display, 0, getTextPositions(display)[line++], gpsStatus);
     }
 
@@ -970,6 +990,9 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
 
         // === Fifth Row: Altitude ===
         char DisplayLineTwo[32] = {0};
+        int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
+                          ? ourNode->position.altitude
+                          : geoCoord.getAltitude();
         if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
             snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
         } else {

From e1634076f2c56427f4f9f411d901c7852d544cb4 Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Fri, 5 Sep 2025 22:21:33 -0500
Subject: [PATCH 62/81] Fix date display to be upper right bound (#7876)

---
 src/graphics/draw/ClockRenderer.cpp | 34 ++++++++++++++++++++++++-----
 1 file changed, 29 insertions(+), 5 deletions(-)

diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index d046bda6f..6d3f13842 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -302,14 +302,28 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
                         secondString);
 #endif
 
+    display->setFont(FONT_SMALL);
     // Display GPS derived date
     char datetimeStr[25];
     UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
     char fullLine[40];
-    snprintf(fullLine, sizeof(fullLine), "%s", datetimeStr);
-    yOffset = (isHighResolution) ? 12 : 1;
-    display->drawString(startingHourMinuteTextX + timeStringWidth - display->getStringWidth(fullLine),
-                        getTextPositions(display)[line] + yOffset, fullLine);
+    xOffset = 1;
+    if (isHighResolution) {
+        snprintf(fullLine, sizeof(fullLine), "%s", datetimeStr);
+    } else {
+        snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
+    }
+    if (hasUnreadMessage) {
+        if (isHighResolution) {
+            xOffset = 23;
+            snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
+        } else {
+            xOffset = 15;
+            snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[5]);
+        }
+    }
+    display->drawString(display->getWidth() - xOffset - display->getStringWidth(fullLine), getTextPositions(display)[line],
+                        fullLine);
 }
 
 void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
@@ -529,12 +543,22 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
         char datetimeStr[25];
         UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
         char fullLine[40];
+        int xOffset = 1;
         if (isHighResolution) {
             snprintf(fullLine, sizeof(fullLine), "%s", datetimeStr);
         } else {
             snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
         }
-        display->drawString(display->getWidth() - 1 - display->getStringWidth(fullLine), getTextPositions(display)[line],
+        if (hasUnreadMessage) {
+            if (isHighResolution) {
+                xOffset = 23;
+                snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
+            } else {
+                xOffset = 15;
+                snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[5]);
+            }
+        }
+        display->drawString(display->getWidth() - xOffset - display->getStringWidth(fullLine), getTextPositions(display)[line],
                             fullLine);
     }
 }

From e7b74795893cdde6405717abc333e37bc638fd58 Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sat, 6 Sep 2025 05:14:26 -0400
Subject: [PATCH 63/81] Reverting changes made by PR #7520 and adjusting ADC
 (#7878)

* ADC value adjustment for T114
---
 variants/nrf52840/heltec_mesh_node_t114/variant.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h
index b71106a53..7e82733aa 100644
--- a/variants/nrf52840/heltec_mesh_node_t114/variant.h
+++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h
@@ -208,7 +208,7 @@ No longer populated on PCB
 #undef AREF_VOLTAGE
 #define AREF_VOLTAGE 3.0
 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0
-#define ADC_MULTIPLIER (4.99F)
+#define ADC_MULTIPLIER (4.916F)
 
 #define HAS_RTC 0
 #ifdef __cplusplus

From f26e65757787981cd5bc128cb51bcdbd64603110 Mon Sep 17 00:00:00 2001
From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com>
Date: Sat, 6 Sep 2025 06:20:57 -0500
Subject: [PATCH 64/81] Fix esptool detection and baud rate issues in Windows
 batch scripts (#7856)

- Fix esptool detection to use 'version' subcommand instead of no arguments
- Fix device-update.bat to use 115200 bps for flashing, 1200 bps only for reset
- Add missing closing quotes in debug messages

Replace magic numbers with named constants for better maintainability

- Add RESET_BAUD=1200 constant for reset baud rate
- Add UPDATE_OFFSET=0x10000 constant for update flash offset
- Use constants instead of hardcoded values throughout script

Extract magic numbers to constants in shell scripts for consistency

- Add FLASH_BAUD, RESET_BAUD, UPDATE_OFFSET constants to device-update.sh
- Add RESET_BAUD, FIRMWARE_OFFSET constants to device-install.sh
- Replace hardcoded values with named constants throughout
- Maintain consistency with batch script improvements

Fix Python path quoting and remove unreachable code

- Quote Python interpreter paths to handle spaces in paths like 'C:\Program Files\Python\python.exe'
- Remove unreachable GOTO statements after EXIT /B commands
- Improve robustness when custom Python interpreters are specified

Fix esptool detection for pipx installations

- Change from checking ERRORLEVEL GEQ 2 to EQU 9009
- Pipx-installed esptool returns exit code 2 when showing help (normal)
- Only treat Windows 'command not found' error (9009) as truly not found
- Add debug output to show actual exit codes for troubleshooting
---
 bin/device-install.bat |  5 ++---
 bin/device-install.sh  |  8 ++++++--
 bin/device-update.bat  | 19 ++++++++++---------
 bin/device-update.sh   |  9 +++++++--
 4 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/bin/device-install.bat b/bin/device-install.bat
index 24c841e4b..56de4dc10 100755
--- a/bin/device-install.bat
+++ b/bin/device-install.bat
@@ -119,11 +119,10 @@ IF NOT "__%PYTHON%__"=="____" (
 
 CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
 !ESPTOOL_CMD! >nul 2>&1
-IF %ERRORLEVEL% GEQ 2 (
-    @REM esptool exits with code 1 if help is displayed.
+IF %ERRORLEVEL% EQU 9009 (
+    @REM 9009 = command not found on Windows
     CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
     EXIT /B 1
-    GOTO eof
 )
 IF %DEBUG% EQU 1 (
     CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
diff --git a/bin/device-install.sh b/bin/device-install.sh
index c2ba7539a..98937f29a 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -5,6 +5,10 @@ BPS_RESET=false
 TFT_BUILD=false
 MCU=""
 
+# Constants
+RESET_BAUD=1200
+FIRMWARE_OFFSET=0x00
+
 # Variant groups
 BIGDB_8MB=(
 	"picomputer-s3"
@@ -121,7 +125,7 @@ while [ $# -gt 0 ]; do
 done
 
 if [[ $BPS_RESET == true ]]; then
-	$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
+	$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
 	exit 0
 fi
 
@@ -202,7 +206,7 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
 
     echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
     $ESPTOOL_CMD erase-flash
-    $ESPTOOL_CMD write-flash 0x00 "${FILENAME}"
+    $ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
     echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
     $ESPTOOL_CMD write-flash $OTA_OFFSET "${OTAFILE}"
     echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
diff --git a/bin/device-update.bat b/bin/device-update.bat
index 9077ae5b9..a263da992 100755
--- a/bin/device-update.bat
+++ b/bin/device-update.bat
@@ -6,6 +6,8 @@ SET "SCRIPT_NAME=%~nx0"
 SET "DEBUG=0"
 SET "PYTHON="
 SET "ESPTOOL_BAUD=115200"
+SET "RESET_BAUD=1200"
+SET "UPDATE_OFFSET=0x10000"
 SET "ESPTOOL_CMD="
 SET "LOGCOUNTER=0"
 SET "CHANGE_MODE=0"
@@ -85,14 +87,13 @@ IF "!FILENAME:update=!"=="!FILENAME!" (
 )
 
 :skip-filename
-SET "ESPTOOL_BAUD=1200"
 
 CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
 IF NOT "__%PYTHON%__"=="____" (
-    SET "ESPTOOL_CMD=!PYTHON! -m esptool"
+    SET "ESPTOOL_CMD=""!PYTHON!"" -m esptool"
     CALL :LOG_MESSAGE DEBUG "Python interpreter supplied."
 ) ELSE (
-    CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool...
+    CALL :LOG_MESSAGE DEBUG "Python interpreter NOT supplied. Looking for esptool..."
     WHERE esptool >nul 2>&1
     IF %ERRORLEVEL% EQU 0 (
         @REM WHERE exits with code 0 if esptool is found.
@@ -105,11 +106,11 @@ IF NOT "__%PYTHON%__"=="____" (
 
 CALL :LOG_MESSAGE DEBUG "Checking esptool command !ESPTOOL_CMD!..."
 !ESPTOOL_CMD! >nul 2>&1
-IF %ERRORLEVEL% GEQ 2 (
-    @REM esptool exits with code 1 if help is displayed.
+CALL :LOG_MESSAGE DEBUG "esptool exit code: %ERRORLEVEL%"
+IF %ERRORLEVEL% EQU 9009 (
+    @REM 9009 = command not found on Windows
     CALL :LOG_MESSAGE ERROR "esptool not found: !ESPTOOL_CMD!"
     EXIT /B 1
-    GOTO eof
 )
 IF %DEBUG% EQU 1 (
     CALL :LOG_MESSAGE DEBUG "Skipping ESPTOOL_CMD steps."
@@ -127,13 +128,13 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
 
 IF %CHANGE_MODE% EQU 1 (
     @REM Attempt to change mode via 1200bps Reset.
-    CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status
+    CALL :RUN_ESPTOOL !RESET_BAUD! --after no_reset read_flash_status
     GOTO eof
 )
 
 @REM Flashing operations.
-CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET 0x10000..."
-CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash 0x10000 "!FILENAME!" || GOTO eof
+CALL :LOG_MESSAGE INFO "Trying to flash update "!FILENAME!" at OFFSET !UPDATE_OFFSET!..."
+CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !UPDATE_OFFSET! "!FILENAME!" || GOTO eof
 
 CALL :LOG_MESSAGE INFO "Script complete!."
 
diff --git a/bin/device-update.sh b/bin/device-update.sh
index 7f603e070..6f29496e9 100755
--- a/bin/device-update.sh
+++ b/bin/device-update.sh
@@ -3,6 +3,11 @@
 PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
 CHANGE_MODE=false
 
+# Constants
+FLASH_BAUD=115200
+RESET_BAUD=1200
+UPDATE_OFFSET=0x10000
+
 # Determine the correct esptool command to use
 if "$PYTHON" -m esptool version >/dev/null 2>&1; then
     ESPTOOL_CMD="$PYTHON -m esptool"
@@ -64,7 +69,7 @@ done
 shift "$((OPTIND-1))"
 
 if [ "$CHANGE_MODE" = true ]; then
-	$ESPTOOL_CMD --baud 1200 --after no_reset read_flash_status
+	$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
     exit 0
 fi
 
@@ -75,7 +80,7 @@ fi
 
 if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
     echo "Trying to flash update ${FILENAME}"
-    $ESPTOOL_CMD --baud 115200 write-flash 0x10000 "${FILENAME}"
+    $ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
 else
     show_help
     echo "Invalid file: ${FILENAME}"

From 4594ae474e5e63c07b25a410dd9855935c3514de Mon Sep 17 00:00:00 2001
From: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Date: Sat, 6 Sep 2025 13:23:43 +0200
Subject: [PATCH 65/81] =?UTF-8?q?Upon=20receiving=20ACK/reply=20directly,?=
 =?UTF-8?q?=20only=20update=20next-hop=20if=20we=E2=80=99re=20the=20*sole*?=
 =?UTF-8?q?=20relayer=20(#7859)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/mesh/NextHopRouter.cpp |  7 ++++---
 src/mesh/PacketHistory.cpp | 35 +++++++++++++++++++++++++----------
 src/mesh/PacketHistory.h   |  9 +++++++--
 3 files changed, 36 insertions(+), 15 deletions(-)

diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index 794b25aa6..7ceca2195 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -74,10 +74,11 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
         if (p->from != 0) {
             meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
             if (origTx) {
-                // Either relayer of ACK was also a relayer of the packet, or we were the relayer and the ACK came directly from
-                // the destination
+                // Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly
+                // from the destination
                 if (wasRelayer(p->relay_node, p->decoded.request_id, p->to) ||
-                    (wasRelayer(ourRelayID, p->decoded.request_id, p->to) && p->hop_start != 0 && p->hop_start == p->hop_limit)) {
+                    (p->hop_start != 0 && p->hop_start == p->hop_limit &&
+                     wasSoleRelayer(ourRelayID, p->decoded.request_id, p->to))) {
                     if (origTx->next_hop != p->relay_node) { // Not already set
                         LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply", p->from, p->relay_node);
                         origTx->next_hop = p->relay_node;
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 3902c1057..735386d79 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -294,7 +294,7 @@ void PacketHistory::insert(const PacketRecord &r)
 
 /* Check if a certain node was a relayer of a packet in the history given an ID and sender
  * @return true if node was indeed a relayer, false if not */
-bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
+bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole)
 {
     if (!initOk()) {
         LOG_ERROR("PacketHistory - wasRelayer: NOT INITIALIZED!");
@@ -322,27 +322,42 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const uint32_t id, const N
               found->sender, found->id, found->next_hop, millis() - found->rxTimeMsec, found->relayed_by[0], found->relayed_by[1],
               found->relayed_by[2], relayer);
 #endif
-    return wasRelayer(relayer, *found);
+    return wasRelayer(relayer, *found, wasSole);
 }
 
 /* Check if a certain node was a relayer of a packet in the history given iterator
  * @return true if node was indeed a relayer, false if not */
-bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r)
+bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole)
 {
-    for (uint8_t i = 0; i < NUM_RELAYERS; i++) {
+    bool found = false;
+    bool other_present = false;
+
+    for (uint8_t i = 0; i < NUM_RELAYERS; ++i) {
         if (r.relayed_by[i] == relayer) {
-#if VERBOSE_PACKET_HISTORY
-            LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? YES", r.sender, r.id,
-                      r.relayed_by[0], r.relayed_by[1], r.relayed_by[2], relayer);
-#endif
-            return true;
+            found = true;
+        } else if (r.relayed_by[i] != 0) {
+            other_present = true;
         }
     }
+
+    if (wasSole) {
+        *wasSole = (found && !other_present);
+    }
+
 #if VERBOSE_PACKET_HISTORY
     LOG_DEBUG("Packet History - was rel.PR.: s=%08x id=%08x rls=%02x %02x %02x / rl=%02x? NO", r.sender, r.id, r.relayed_by[0],
               r.relayed_by[1], r.relayed_by[2], relayer);
 #endif
-    return false;
+
+    return found;
+}
+
+// Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
+bool PacketHistory::wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
+{
+    bool wasSole = false;
+    wasRelayer(relayer, id, sender, &wasSole);
+    return wasSole;
 }
 
 // Remove a relayer from the list of relayers of a packet in the history given an ID and sender
diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h
index 9f14a4cf0..4b53c8f6a 100644
--- a/src/mesh/PacketHistory.h
+++ b/src/mesh/PacketHistory.h
@@ -34,8 +34,9 @@ class PacketHistory
     void insert(const PacketRecord &r); // Insert or replace a packet record in the history
 
     /* Check if a certain node was a relayer of a packet in the history given iterator
+     * If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
      * @return true if node was indeed a relayer, false if not */
-    bool wasRelayer(const uint8_t relayer, const PacketRecord &r);
+    bool wasRelayer(const uint8_t relayer, const PacketRecord &r, bool *wasSole = nullptr);
 
     PacketHistory(const PacketHistory &);            // non construction-copyable
     PacketHistory &operator=(const PacketHistory &); // non copyable
@@ -54,8 +55,12 @@ class PacketHistory
                          bool *weWereNextHop = nullptr);
 
     /* Check if a certain node was a relayer of a packet in the history given an ID and sender
+     * If wasSole is not nullptr, it will be set to true if the relayer was the only relayer of that packet
      * @return true if node was indeed a relayer, false if not */
-    bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
+    bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole = nullptr);
+
+    // Check if a certain node was the *only* relayer of a packet in the history given an ID and sender
+    bool wasSoleRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
 
     // Remove a relayer from the list of relayers of a packet in the history given an ID and sender
     void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);

From 37d14f942e63acebbf1950f41bcea4f8689758c3 Mon Sep 17 00:00:00 2001
From: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Date: Sat, 6 Sep 2025 05:14:26 -0400
Subject: [PATCH 66/81] Reverting changes made by PR #7520 and adjusting ADC
 (#7878)

* ADC value adjustment for T114
---
 variants/nrf52840/heltec_mesh_node_t114/variant.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h
index b71106a53..7e82733aa 100644
--- a/variants/nrf52840/heltec_mesh_node_t114/variant.h
+++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h
@@ -208,7 +208,7 @@ No longer populated on PCB
 #undef AREF_VOLTAGE
 #define AREF_VOLTAGE 3.0
 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0
-#define ADC_MULTIPLIER (4.99F)
+#define ADC_MULTIPLIER (4.916F)
 
 #define HAS_RTC 0
 #ifdef __cplusplus

From b6eeccadeb5ff4066de078d997ff5d2b2ba9ef1d Mon Sep 17 00:00:00 2001
From: Jason P 
Date: Sun, 7 Sep 2025 14:34:07 -0500
Subject: [PATCH 67/81] Show GPS Date properly in drawCommonHeader (#7887)

* Commit good code that is sustainable

* Fix new build errors
---
 src/graphics/SharedUIDisplay.cpp              | 47 ++++++++++++----
 src/graphics/SharedUIDisplay.h                |  3 +-
 src/graphics/draw/ClockRenderer.cpp           | 53 +------------------
 src/graphics/draw/UIRenderer.h                |  1 +
 .../Telemetry/EnvironmentTelemetry.cpp        |  3 +-
 src/modules/Telemetry/PowerTelemetry.cpp      |  3 +-
 6 files changed, 46 insertions(+), 64 deletions(-)

diff --git a/src/graphics/SharedUIDisplay.cpp b/src/graphics/SharedUIDisplay.cpp
index 53fb8e993..3cf84f415 100644
--- a/src/graphics/SharedUIDisplay.cpp
+++ b/src/graphics/SharedUIDisplay.cpp
@@ -1,6 +1,7 @@
 #include "graphics/SharedUIDisplay.h"
 #include "RTC.h"
 #include "graphics/ScreenFonts.h"
+#include "graphics/draw/UIRenderer.h"
 #include "main.h"
 #include "meshtastic/config.pb.h"
 #include "power.h"
@@ -57,7 +58,7 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
 // *************************
 // * Common Header Drawing *
 // *************************
-void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only)
+void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, bool show_date)
 {
     constexpr int HEADER_OFFSET_Y = 1;
     y += HEADER_OFFSET_Y;
@@ -73,7 +74,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
     const int screenW = display->getWidth();
     const int screenH = display->getHeight();
 
-    if (!battery_only) {
+    if (!force_no_invert) {
         // === Inverted Header Background ===
         if (isInverted) {
             display->setColor(BLACK);
@@ -191,13 +192,28 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
     int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
     int timeX = screenW - xOffset - timeStrWidth + 4;
 
-    if (rtc_sec > 0 && !battery_only) {
+    if (rtc_sec > 0) {
         // === Build Time String ===
         long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
         int hour = hms / SEC_PER_HOUR;
         int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
         snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
 
+        // === Build Date String ===
+        char datetimeStr[25];
+        UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
+        char dateLine[40];
+
+        if (isHighResolution) {
+            snprintf(dateLine, sizeof(dateLine), "%s", datetimeStr);
+        } else {
+            if (hasUnreadMessage) {
+                snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[5]);
+            } else {
+                snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[2]);
+            }
+        }
+
         if (config.display.use_12h_clock) {
             bool isPM = hour >= 12;
             hour %= 12;
@@ -206,7 +222,11 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
             snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
         }
 
-        timeStrWidth = display->getStringWidth(timeStr);
+        if (show_date) {
+            timeStrWidth = display->getStringWidth(dateLine);
+        } else {
+            timeStrWidth = display->getStringWidth(timeStr);
+        }
         timeX = screenW - xOffset - timeStrWidth + 3;
 
         // === Show Mail or Mute Icon to the Left of Time ===
@@ -233,7 +253,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
                 int iconW = 16, iconH = 12;
                 int iconX = iconRightEdge - iconW;
                 int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
-                if (isInverted) {
+                if (isInverted && !force_no_invert) {
                     display->setColor(WHITE);
                     display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
                     display->setColor(BLACK);
@@ -248,7 +268,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
             } else {
                 int iconX = iconRightEdge - (mail_width - 2);
                 int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
-                if (isInverted) {
+                if (isInverted && !force_no_invert) {
                     display->setColor(WHITE);
                     display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
                     display->setColor(BLACK);
@@ -291,10 +311,17 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
             }
         }
 
-        // === Draw Time ===
-        display->drawString(timeX, textY, timeStr);
-        if (isBold)
-            display->drawString(timeX - 1, textY, timeStr);
+        if (show_date) {
+            // === Draw Date ===
+            display->drawString(timeX, textY, dateLine);
+            if (isBold)
+                display->drawString(timeX - 1, textY, dateLine);
+        } else {
+            // === Draw Time ===
+            display->drawString(timeX, textY, timeStr);
+            if (isBold)
+                display->drawString(timeX - 1, textY, timeStr);
+        }
 
     } else {
         // === No Time Available: Mail/Mute Icon Moves to Far Right ===
diff --git a/src/graphics/SharedUIDisplay.h b/src/graphics/SharedUIDisplay.h
index b8d82795e..e1a7c6383 100644
--- a/src/graphics/SharedUIDisplay.h
+++ b/src/graphics/SharedUIDisplay.h
@@ -49,7 +49,8 @@ void determineResolution(int16_t screenheight, int16_t screenwidth);
 void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
 
 // Shared battery/time/mail header
-void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool battery_only = false);
+void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
+                      bool show_date = false);
 
 const int *getTextPositions(OLEDDisplay *display);
 
diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
index 6d3f13842..bb8cdd561 100644
--- a/src/graphics/draw/ClockRenderer.cpp
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -2,7 +2,6 @@
 #if HAS_SCREEN
 #include "ClockRenderer.h"
 #include "NodeDB.h"
-#include "UIRenderer.h"
 #include "configuration.h"
 #include "gps/GeoCoord.h"
 #include "gps/RTC.h"
@@ -190,8 +189,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
     // === Set Title, Blank for Clock
     const char *titleStr = "";
     // === Header ===
-    graphics::drawCommonHeader(display, x, y, titleStr, true);
-    int line = 0;
+    graphics::drawCommonHeader(display, x, y, titleStr, true, true);
 
 #ifdef T_WATCH_S3
     if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
@@ -301,29 +299,6 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
     display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
                         secondString);
 #endif
-
-    display->setFont(FONT_SMALL);
-    // Display GPS derived date
-    char datetimeStr[25];
-    UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
-    char fullLine[40];
-    xOffset = 1;
-    if (isHighResolution) {
-        snprintf(fullLine, sizeof(fullLine), "%s", datetimeStr);
-    } else {
-        snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
-    }
-    if (hasUnreadMessage) {
-        if (isHighResolution) {
-            xOffset = 23;
-            snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
-        } else {
-            xOffset = 15;
-            snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[5]);
-        }
-    }
-    display->drawString(display->getWidth() - xOffset - display->getStringWidth(fullLine), getTextPositions(display)[line],
-                        fullLine);
 }
 
 void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
@@ -338,8 +313,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
     // === Set Title, Blank for Clock
     const char *titleStr = "";
     // === Header ===
-    graphics::drawCommonHeader(display, x, y, titleStr, true);
-    int line = 0;
+    graphics::drawCommonHeader(display, x, y, titleStr, true, true);
 
 #ifdef T_WATCH_S3
     if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
@@ -537,29 +511,6 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
         // draw second hand
         display->drawLine(centerX, centerY, secondX, secondY);
 #endif
-
-        display->setFont(FONT_SMALL);
-        // Display GPS derived date
-        char datetimeStr[25];
-        UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
-        char fullLine[40];
-        int xOffset = 1;
-        if (isHighResolution) {
-            snprintf(fullLine, sizeof(fullLine), "%s", datetimeStr);
-        } else {
-            snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
-        }
-        if (hasUnreadMessage) {
-            if (isHighResolution) {
-                xOffset = 23;
-                snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[2]);
-            } else {
-                xOffset = 15;
-                snprintf(fullLine, sizeof(fullLine), "%s", &datetimeStr[5]);
-            }
-        }
-        display->drawString(display->getWidth() - xOffset - display->getStringWidth(fullLine), getTextPositions(display)[line],
-                            fullLine);
     }
 }
 
diff --git a/src/graphics/draw/UIRenderer.h b/src/graphics/draw/UIRenderer.h
index 3c8e1dd9d..eada150f9 100644
--- a/src/graphics/draw/UIRenderer.h
+++ b/src/graphics/draw/UIRenderer.h
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "NodeDB.h"
 #include "graphics/Screen.h"
 #include "graphics/emotes.h"
 #include 
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index c90d9250f..8ac160f8b 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -30,7 +30,8 @@
 
 namespace graphics
 {
-extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only);
+extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert,
+                             bool show_date);
 }
 #if __has_include()
 #include "Sensor/AHT10.h"
diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp
index 35409edef..479861a2e 100644
--- a/src/modules/Telemetry/PowerTelemetry.cpp
+++ b/src/modules/Telemetry/PowerTelemetry.cpp
@@ -24,7 +24,8 @@
 
 namespace graphics
 {
-extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only);
+extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert,
+                             bool show_date);
 }
 
 int32_t PowerTelemetryModule::runOnce()

From 9c6544ebfa8248412d1008f0d32c986c1dc99d38 Mon Sep 17 00:00:00 2001
From: Dmitry Dubinin <4762973+capricornusx@users.noreply.github.com>
Date: Mon, 8 Sep 2025 00:15:27 +0300
Subject: [PATCH 68/81] Fix excluded modules configuration handling (#7838)

* Fix excluded modules configuration handling

- Add excluded_modules flags in getDeviceMetadata() for MQTT, PAXCOUNTER, STOREFORWARD, RANGETEST, NEIGHBORINFO
- Add conditional compilation guards in AdminModule for RANGETEST, AUDIO, PAXCOUNTER, STOREFORWARD, EXTNOTIF, DETECTIONSENSOR, AMBIENTLIGHTING
- Add skip logic in PhoneAPI for excluded modules during config enumeration
- Add conditional has_* flags in NodeDB only for included modules

Fixes issue where excluded modules still appeared in client applications and sometimes caused PAYLOADVARIANT_NOT_SET errors.

* Fix excluded modules issues and refactor code

- Restore original PAXCOUNTER logic: only exclude on non-ESP32 platforms due to memory constraints
- Fix has_store_forward flag to be conditionally compiled based on MESHTASTIC_EXCLUDE_STOREFORWARD
- Refactor PhoneAPI module config skipping logic to use helper function skipExcludedModuleConfig()
- Reduce code duplication in PhoneAPI by extracting common skip logic

This addresses the three issues identified in the code review:
1. PAXCOUNTER memory impact on non-ESP32 devices
2. Unconditional has_store_forward flag setting
3. Duplicated state management logic across multiple #else blocks

* Fix ambient lighting module exclusion in PhoneAPI and AdminModule

- Add conditional compilation guards for ambient lighting in PhoneAPI.cpp
- Replace old HAS_RGB_LED logic with MESHTASTIC_EXCLUDE_AMBIENTLIGHTING check in AdminModule.cpp
- Ensure ambient lighting module is properly excluded when MESHTASTIC_EXCLUDE_AMBIENTLIGHTING=1
---
 src/configuration.h         |  1 +
 src/main.cpp                | 18 +++++++++++++--
 src/mesh/NodeDB.cpp         | 20 +++++++++++++++++
 src/mesh/PhoneAPI.cpp       | 45 +++++++++++++++++++++++++++++++++++++
 src/mesh/PhoneAPI.h         |  3 +++
 src/modules/AdminModule.cpp | 29 ++++++++++++++++++++++++
 6 files changed, 114 insertions(+), 2 deletions(-)

diff --git a/src/configuration.h b/src/configuration.h
index 81632c89e..d5adba028 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -431,6 +431,7 @@ along with this program.  If not, see .
 #define MESHTASTIC_EXCLUDE_SERIAL 1
 #define MESHTASTIC_EXCLUDE_POWERSTRESS 1
 #define MESHTASTIC_EXCLUDE_ADMIN 1
+#define MESHTASTIC_EXCLUDE_AMBIENTLIGHTING 1
 #endif
 
 // // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)
diff --git a/src/main.cpp b/src/main.cpp
index 6667f9f17..8d576f008 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1506,6 +1506,9 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
     deviceMetadata.hw_model = HW_VENDOR;
     deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled;
     deviceMetadata.excluded_modules = meshtastic_ExcludedModules_EXCLUDED_NONE;
+#if MESHTASTIC_EXCLUDE_MQTT
+    deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_MQTT_CONFIG;
+#endif
 #if MESHTASTIC_EXCLUDE_REMOTEHARDWARE
     deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_REMOTEHARDWARE_CONFIG;
 #endif
@@ -1528,10 +1531,21 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
 #if NO_EXT_GPIO && NO_GPS || MESHTASTIC_EXCLUDE_SERIAL
     deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_SERIAL_CONFIG;
 #endif
-#ifndef ARCH_ESP32
+#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_PAXCOUNTER
+    // PAXCOUNTER is only supported on ESP32 due to memory constraints
+#else
     deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_PAXCOUNTER_CONFIG;
 #endif
-#if !defined(HAS_RGB_LED) && !RAK_4631
+#if MESHTASTIC_EXCLUDE_STOREFORWARD
+    deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_STOREFORWARD_CONFIG;
+#endif
+#if MESHTASTIC_EXCLUDE_RANGETEST
+    deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_RANGETEST_CONFIG;
+#endif
+#if MESHTASTIC_EXCLUDE_NEIGHBORINFO
+    deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_NEIGHBORINFO_CONFIG;
+#endif
+#if (!defined(HAS_RGB_LED) && !defined(RAK_4631)) || defined(MESHTASTIC_EXCLUDE_AMBIENTLIGHTING)
     deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AMBIENTLIGHTING_CONFIG;
 #endif
 
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 2ab6fda59..52a18a53f 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -775,7 +775,9 @@ void NodeDB::installDefaultModuleConfig()
 
     moduleConfig.version = DEVICESTATE_CUR_VER;
     moduleConfig.has_mqtt = true;
+#if !MESHTASTIC_EXCLUDE_RANGETEST
     moduleConfig.has_range_test = true;
+#endif
     moduleConfig.has_serial = true;
     moduleConfig.has_store_forward = true;
     moduleConfig.has_telemetry = true;
@@ -841,6 +843,12 @@ void NodeDB::installDefaultModuleConfig()
     moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
 #endif
     moduleConfig.has_canned_message = true;
+#if !MESHTASTIC_EXCLUDE_AUDIO
+    moduleConfig.has_audio = true;
+#endif
+#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
+    moduleConfig.has_paxcounter = true;
+#endif
 #if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT
     moduleConfig.mqtt.enabled = true;
 #endif
@@ -883,12 +891,14 @@ void NodeDB::installDefaultModuleConfig()
     moduleConfig.detection_sensor.detection_trigger_type = meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH;
     moduleConfig.detection_sensor.minimum_broadcast_secs = 45;
 
+#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
     moduleConfig.has_ambient_lighting = 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;
+#endif
 
     initModuleConfigIntervals();
 }
@@ -1428,15 +1438,25 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
         moduleConfig.has_canned_message = true;
         moduleConfig.has_external_notification = true;
         moduleConfig.has_mqtt = true;
+#if !MESHTASTIC_EXCLUDE_RANGETEST
         moduleConfig.has_range_test = true;
+#endif
         moduleConfig.has_serial = true;
+#if !MESHTASTIC_EXCLUDE_STOREFORWARD
         moduleConfig.has_store_forward = true;
+#endif
         moduleConfig.has_telemetry = true;
         moduleConfig.has_neighbor_info = true;
         moduleConfig.has_detection_sensor = true;
+#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
         moduleConfig.has_ambient_lighting = true;
+#endif
+#if !MESHTASTIC_EXCLUDE_AUDIO
         moduleConfig.has_audio = true;
+#endif
+#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
         moduleConfig.has_paxcounter = true;
+#endif
 
         success &=
             saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index a3a8a2087..d11eff9e7 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -34,6 +34,21 @@
 // Flag to indicate a heartbeat was received and we should send queue status
 bool heartbeatReceived = false;
 
+// Helper function to skip excluded module configs and advance state
+size_t PhoneAPI::skipExcludedModuleConfig(uint8_t *buf)
+{
+    config_state++;
+    if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) {
+        if (config_nonce == SPECIAL_NONCE_ONLY_CONFIG) {
+            state = STATE_SEND_FILEMANIFEST;
+        } else {
+            state = STATE_SEND_OTHER_NODEINFOS;
+        }
+        config_state = 0;
+    }
+    return getFromRadio(buf);
+}
+
 PhoneAPI::PhoneAPI()
 {
     lastContactMsec = millis();
@@ -354,20 +369,35 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
             fromRadioScratch.moduleConfig.payload_variant.serial = moduleConfig.serial;
             break;
         case meshtastic_ModuleConfig_external_notification_tag:
+#if !(NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION)
             LOG_DEBUG("Send module config: ext notification");
             fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_external_notification_tag;
             fromRadioScratch.moduleConfig.payload_variant.external_notification = moduleConfig.external_notification;
             break;
+#else
+            LOG_DEBUG("External Notification module excluded from build, skipping");
+            return skipExcludedModuleConfig(buf);
+#endif
         case meshtastic_ModuleConfig_store_forward_tag:
+#if !MESHTASTIC_EXCLUDE_STOREFORWARD
             LOG_DEBUG("Send module config: store forward");
             fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_store_forward_tag;
             fromRadioScratch.moduleConfig.payload_variant.store_forward = moduleConfig.store_forward;
             break;
+#else
+            LOG_DEBUG("Store & Forward module excluded from build, skipping");
+            return skipExcludedModuleConfig(buf);
+#endif
         case meshtastic_ModuleConfig_range_test_tag:
+#if !MESHTASTIC_EXCLUDE_RANGETEST
             LOG_DEBUG("Send module config: range test");
             fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_range_test_tag;
             fromRadioScratch.moduleConfig.payload_variant.range_test = moduleConfig.range_test;
             break;
+#else
+            LOG_DEBUG("Range Test module excluded from build, skipping");
+            return skipExcludedModuleConfig(buf);
+#endif
         case meshtastic_ModuleConfig_telemetry_tag:
             LOG_DEBUG("Send module config: telemetry");
             fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_telemetry_tag;
@@ -379,10 +409,15 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
             fromRadioScratch.moduleConfig.payload_variant.canned_message = moduleConfig.canned_message;
             break;
         case meshtastic_ModuleConfig_audio_tag:
+#if !MESHTASTIC_EXCLUDE_AUDIO
             LOG_DEBUG("Send module config: audio");
             fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_audio_tag;
             fromRadioScratch.moduleConfig.payload_variant.audio = moduleConfig.audio;
             break;
+#else
+            LOG_DEBUG("Audio module excluded from build, skipping");
+            return skipExcludedModuleConfig(buf);
+#endif
         case meshtastic_ModuleConfig_remote_hardware_tag:
             LOG_DEBUG("Send module config: remote hardware");
             fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_remote_hardware_tag;
@@ -399,15 +434,25 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
             fromRadioScratch.moduleConfig.payload_variant.detection_sensor = moduleConfig.detection_sensor;
             break;
         case meshtastic_ModuleConfig_ambient_lighting_tag:
+#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
             LOG_DEBUG("Send module config: ambient lighting");
             fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag;
             fromRadioScratch.moduleConfig.payload_variant.ambient_lighting = moduleConfig.ambient_lighting;
             break;
+#else
+            LOG_DEBUG("Ambient Lighting module excluded from build, skipping");
+            return skipExcludedModuleConfig(buf);
+#endif
         case meshtastic_ModuleConfig_paxcounter_tag:
+#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
             LOG_DEBUG("Send module config: paxcounter");
             fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
             fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter;
             break;
+#else
+            LOG_DEBUG("Paxcounter module excluded from build, skipping");
+            return skipExcludedModuleConfig(buf);
+#endif
         default:
             LOG_ERROR("Unknown module config type %d", config_state);
         }
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index 0d7772d17..6b4bb6fc1 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -172,4 +172,7 @@ class PhoneAPI
 
     /// If the mesh service tells us fromNum has changed, tell the phone
     virtual int onNotify(uint32_t newValue) override;
+
+    /// Helper function to skip excluded module configs and advance state
+    size_t skipExcludedModuleConfig(uint8_t *buf);
 };
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 407003f7e..78c101765 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -1040,19 +1040,32 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
             res.get_module_config_response.payload_variant.serial = moduleConfig.serial;
             break;
         case meshtastic_AdminMessage_ModuleConfigType_EXTNOTIF_CONFIG:
+#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
             LOG_INFO("Get module config: External Notification");
             res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_external_notification_tag;
             res.get_module_config_response.payload_variant.external_notification = moduleConfig.external_notification;
+#else
+            LOG_DEBUG("External Notification module excluded from build, skipping config");
+#endif
             break;
         case meshtastic_AdminMessage_ModuleConfigType_STOREFORWARD_CONFIG:
+#if !MESHTASTIC_EXCLUDE_STOREFORWARD
             LOG_INFO("Get module config: Store & Forward");
             res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_store_forward_tag;
             res.get_module_config_response.payload_variant.store_forward = moduleConfig.store_forward;
+#else
+            LOG_DEBUG("Store & Forward module excluded from build, skipping config");
+#endif
             break;
         case meshtastic_AdminMessage_ModuleConfigType_RANGETEST_CONFIG:
+#if !MESHTASTIC_EXCLUDE_RANGETEST
             LOG_INFO("Get module config: Range Test");
             res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_range_test_tag;
             res.get_module_config_response.payload_variant.range_test = moduleConfig.range_test;
+#else
+            LOG_DEBUG("Range Test module excluded from build, skipping config");
+            // Don't set payload variant - will result in empty response
+#endif
             break;
         case meshtastic_AdminMessage_ModuleConfigType_TELEMETRY_CONFIG:
             LOG_INFO("Get module config: Telemetry");
@@ -1065,9 +1078,13 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
             res.get_module_config_response.payload_variant.canned_message = moduleConfig.canned_message;
             break;
         case meshtastic_AdminMessage_ModuleConfigType_AUDIO_CONFIG:
+#if !MESHTASTIC_EXCLUDE_AUDIO
             LOG_INFO("Get module config: Audio");
             res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_audio_tag;
             res.get_module_config_response.payload_variant.audio = moduleConfig.audio;
+#else
+            LOG_DEBUG("Audio module excluded from build, skipping config");
+#endif
             break;
         case meshtastic_AdminMessage_ModuleConfigType_REMOTEHARDWARE_CONFIG:
             LOG_INFO("Get module config: Remote Hardware");
@@ -1080,19 +1097,31 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
             res.get_module_config_response.payload_variant.neighbor_info = moduleConfig.neighbor_info;
             break;
         case meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG:
+#if !(NO_EXT_GPIO || MESHTASTIC_EXCLUDE_DETECTIONSENSOR)
             LOG_INFO("Get module config: Detection Sensor");
             res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_detection_sensor_tag;
             res.get_module_config_response.payload_variant.detection_sensor = moduleConfig.detection_sensor;
+#else
+            LOG_DEBUG("Detection Sensor module excluded from build, skipping config");
+#endif
             break;
         case meshtastic_AdminMessage_ModuleConfigType_AMBIENTLIGHTING_CONFIG:
+#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
             LOG_INFO("Get module config: Ambient Lighting");
             res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag;
             res.get_module_config_response.payload_variant.ambient_lighting = moduleConfig.ambient_lighting;
+#else
+            LOG_DEBUG("Ambient Lighting module excluded from build, skipping config");
+#endif
             break;
         case meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG:
+#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
             LOG_INFO("Get module config: Paxcounter");
             res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
             res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter;
+#else
+            LOG_DEBUG("Paxcounter module excluded from build, skipping config");
+#endif
             break;
         }
 

From f6ba9604a7543eb0a3b444caf0a44e0d03da22cc Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 09:46:26 +1000
Subject: [PATCH 69/81] Trunk fix (#7898)


From 81cb1e427fa64fc98b6e8dddb719d00a26528e23 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 10:29:26 +1000
Subject: [PATCH 70/81] 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
---
 src/gps/RTC.cpp | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index e208e2df9..39b633e47 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -130,11 +130,15 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
     uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
 #ifdef BUILD_EPOCH
     if (tv->tv_sec < BUILD_EPOCH) {
+#ifdef GPS_DEBUG
         LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+#endif
         return RTCSetResultInvalidTime;
     } else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
+#ifdef GPS_DEBUG
         LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
                  BUILD_EPOCH + FORTY_YEARS);
+#endif
         return RTCSetResultInvalidTime;
     }
 #endif
@@ -252,11 +256,15 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
     uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
 #ifdef BUILD_EPOCH
     if (tv.tv_sec < BUILD_EPOCH) {
+#ifdef GPS_DEBUG
         LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+#endif
         return RTCSetResultInvalidTime;
     } else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
+#ifdef GPS_DEBUG
         LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
                  BUILD_EPOCH + FORTY_YEARS);
+#endif
         return RTCSetResultInvalidTime;
     }
 #endif

From 77acbc6814ab68f07d93932ea2b79c472f4dfb9e Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 10:29:40 +1000
Subject: [PATCH 71/81] Add EPOCH_BUILD to latest setup step. (#7894)

Previously this was in setup-base. However, setup-base is no longer
used by the setup job.

Fixes https://github.com/meshtastic/gh-action-firmware/issues/10
Fixes https://github.com/meshtastic/firmware/issues/7888
---
 .github/workflows/main_matrix.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index ed14907dc..fa780aaf6 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -48,6 +48,10 @@ jobs:
           python-version: 3.x
           cache: pip
       - run: pip install -U platformio
+      - name: Uncomment build epoch
+        shell: bash
+        run: |
+          sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
       - name: Generate matrix
         id: jsonStep
         run: |

From c92fa6aa8a89227c172c0b33f29e43ee86a2a80d Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 10:31:33 +1000
Subject: [PATCH 72/81] chore(deps): update meshtastic/device-ui digest to
 a04bc94 (#7857) (#7900)

* chore(deps): update meshtastic/device-ui digest to a04bc94 (#7857)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Fix INA3221 higher current wrong readings (#7607)

* chore(deps): update meshtastic/device-ui digest to 10f0244 (#7840)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* use branch of ina3221 library with fixes

* using commit hash instead of branch name

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Marco Veneziano 
---
 platformio.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/platformio.ini b/platformio.ini
index 61880c709..c58b14db1 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -118,7 +118,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/10f02441ec7dcd099c4c5165c709afc3e0e3cb88.zip
+	https://github.com/meshtastic/device-ui/archive/a04bc94b45dacdabf3ae1832d4591390e35fc61f.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From f8b160595f573f71fbbd1345807fbcbc4e6a4d3f Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 11:02:29 +1000
Subject: [PATCH 73/81] Fix merge conflict with test changes (#7902)

https://github.com/meshtastic/firmware/pull/7862/commits/289f90bdbec72096ce9fb99eaf5587827245126a

merged a commit that relied on

https://github.com/meshtastic/firmware/commit/5b9db81819f45b625683047c3b78bfece8d23b2e

but the latter commit was not merged.

This does manual wrangling to make sure the same file that exists on develop
right now ends up on master.
---
 .../ports/test_encrypted.cpp                  | 74 ++-----------------
 .../test_serializer.cpp                       | 10 +++
 2 files changed, 17 insertions(+), 67 deletions(-)

diff --git a/test/test_meshpacket_serializer/ports/test_encrypted.cpp b/test/test_meshpacket_serializer/ports/test_encrypted.cpp
index 9efc2fb1b..37cfc1626 100644
--- a/test/test_meshpacket_serializer/ports/test_encrypted.cpp
+++ b/test/test_meshpacket_serializer/ports/test_encrypted.cpp
@@ -1,27 +1,5 @@
 #include "../test_helpers.h"
 
-// test data initialization
-const int from = 0x11223344;
-const int to = 0x55667788;
-const int id = 0x9999;
-
-// Helper function to create a test encrypted packet
-meshtastic_MeshPacket create_test_encrypted_packet(uint32_t from, uint32_t to, uint32_t id, const char *data)
-{
-    meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero;
-    packet.from = from;
-    packet.to = to;
-    packet.id = id;
-    packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag;
-
-    if (data) {
-        packet.encrypted.size = strlen(data);
-        memcpy(packet.encrypted.bytes, data, packet.encrypted.size);
-    }
-
-    return packet;
-}
-
 // Helper function for all encrypted packet assertions
 void assert_encrypted_packet(const std::string &json, meshtastic_MeshPacket packet)
 {
@@ -61,58 +39,20 @@ void assert_encrypted_packet(const std::string &json, meshtastic_MeshPacket pack
 // Test encrypted packet serialization
 void test_encrypted_packet_serialization()
 {
-    meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero;
-    packet.from = 0x11223344;
-    packet.to = 0x55667788;
-    packet.id = 0x9999;
-    packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag;
-
-    // Add some dummy encrypted data
-    const char *encrypted_data = "encrypted_payload_data";
-    packet.encrypted.size = strlen(encrypted_data);
-    memcpy(packet.encrypted.bytes, encrypted_data, packet.encrypted.size);
-
+    const char *data = "encrypted_payload_data";
+    meshtastic_MeshPacket packet =
+        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast(data), strlen(data),
+                           meshtastic_MeshPacket_encrypted_tag);
     std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
-    TEST_ASSERT_TRUE(json.length() > 0);
 
-    JSONValue *root = JSON::Parse(json.c_str());
-    TEST_ASSERT_NOT_NULL(root);
-    TEST_ASSERT_TRUE(root->IsObject());
-
-    JSONObject jsonObj = root->AsObject();
-
-    // Check basic packet fields
-    TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
-    TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber());
-
-    TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
-    TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber());
-
-    TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
-    TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber());
-
-    // Check that it has encrypted data fields (not "payload" but "bytes" and "size")
-    TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end());
-    TEST_ASSERT_TRUE(jsonObj["bytes"]->IsString());
-
-    TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end());
-    TEST_ASSERT_EQUAL(22, (int)jsonObj["size"]->AsNumber()); // strlen("encrypted_payload_data") = 22
-
-    // The encrypted data should be hex-encoded
-    std::string encrypted_hex = jsonObj["bytes"]->AsString();
-    TEST_ASSERT_TRUE(encrypted_hex.length() > 0);
-    // Should be twice the size of the original data (hex encoding)
-    TEST_ASSERT_EQUAL(44, encrypted_hex.length()); // 22 * 2 = 44
-
-    delete root;
+    assert_encrypted_packet(json, packet);
 }
 
 // Test empty encrypted packet
 void test_empty_encrypted_packet()
 {
-    const char *data = "";
-
-    meshtastic_MeshPacket packet = create_test_encrypted_packet(from, to, id, data);
+    meshtastic_MeshPacket packet =
+        create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0, meshtastic_MeshPacket_encrypted_tag);
     std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
 
     assert_encrypted_packet(json, packet);
diff --git a/test/test_meshpacket_serializer/test_serializer.cpp b/test/test_meshpacket_serializer/test_serializer.cpp
index d74031fa4..484db8d74 100644
--- a/test/test_meshpacket_serializer/test_serializer.cpp
+++ b/test/test_meshpacket_serializer/test_serializer.cpp
@@ -4,6 +4,10 @@
 
 // Forward declarations for test functions
 void test_text_message_serialization();
+void test_text_message_serialization_null();
+void test_text_message_serialization_long_text();
+void test_text_message_serialization_oversized();
+void test_text_message_serialization_invalid_utf8();
 void test_position_serialization();
 void test_nodeinfo_serialization();
 void test_waypoint_serialization();
@@ -14,6 +18,7 @@ void test_telemetry_environment_metrics_missing_fields();
 void test_telemetry_environment_metrics_complete_coverage();
 void test_telemetry_environment_metrics_unset_fields();
 void test_encrypted_packet_serialization();
+void test_empty_encrypted_packet();
 
 void setup()
 {
@@ -21,6 +26,10 @@ void setup()
 
     // Text message tests
     RUN_TEST(test_text_message_serialization);
+    RUN_TEST(test_text_message_serialization_null);
+    RUN_TEST(test_text_message_serialization_long_text);
+    RUN_TEST(test_text_message_serialization_oversized);
+    RUN_TEST(test_text_message_serialization_invalid_utf8);
 
     // Position tests
     RUN_TEST(test_position_serialization);
@@ -41,6 +50,7 @@ void setup()
 
     // Encrypted packet test
     RUN_TEST(test_encrypted_packet_serialization);
+    RUN_TEST(test_empty_encrypted_packet);
 
     UNITY_END();
 }

From 7c1eff54fb0e5a558997c46133c87909137eeb99 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 11:05:19 +1000
Subject: [PATCH 74/81] Update protobufs (#7901)

* Update protobufs

* Update protobufs (#7831)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
---
 protobufs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/protobufs b/protobufs
index 4c4427c4a..a84657c22 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 4c4427c4a73c86fed7dc8632188bb8be95349d81
+Subproject commit a84657c220421536f18d11fc5edf680efadbceeb

From 7b854fb5ca78773214fd99319d48a35dfb47d23b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 8 Sep 2025 11:12:52 +1000
Subject: [PATCH 75/81] Update protobufs (#7903)

Co-authored-by: fifieldt <1287116+fifieldt@users.noreply.github.com>
---
 protobufs                                 |  2 +-
 src/mesh/generated/meshtastic/config.pb.h | 11 ++++++++---
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/protobufs b/protobufs
index 07d6573e1..a84657c22 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 07d6573e1065344e80845de704885f011e515233
+Subproject commit a84657c220421536f18d11fc5edf680efadbceeb
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 67d461611..59e55db3f 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -64,7 +64,12 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
     in areas not already covered by other routers, or to bridge around problematic terrain,
     but should not be given priority over other routers in order to avoid unnecessaraily
     consuming hops. */
-    meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11
+    meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11,
+    /* Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
+ Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
+    from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
+    where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node. */
+    meshtastic_Config_DeviceConfig_Role_CLIENT_BASE = 12
 } meshtastic_Config_DeviceConfig_Role;
 
 /* Defines the device's behavior for how messages are rebroadcast */
@@ -646,8 +651,8 @@ extern "C" {
 
 /* Helper constants for enums */
 #define _meshtastic_Config_DeviceConfig_Role_MIN meshtastic_Config_DeviceConfig_Role_CLIENT
-#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_ROUTER_LATE
-#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_ROUTER_LATE+1))
+#define _meshtastic_Config_DeviceConfig_Role_MAX meshtastic_Config_DeviceConfig_Role_CLIENT_BASE
+#define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_CLIENT_BASE+1))
 
 #define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL
 #define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY

From fb59d68eddf9271d39392af92a60bb0de5f92008 Mon Sep 17 00:00:00 2001
From: Manuel <71137295+mverch67@users.noreply.github.com>
Date: Mon, 8 Sep 2025 12:45:11 +0200
Subject: [PATCH 76/81] fix uninitialized kbchar (#7889)

---
 src/input/RotaryEncoderImpl.cpp | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp
index e00c1cc6f..7d638dd71 100644
--- a/src/input/RotaryEncoderImpl.cpp
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -40,10 +40,7 @@ bool RotaryEncoderImpl::init()
 
 int32_t RotaryEncoderImpl::runOnce()
 {
-    InputEvent e;
-    e.inputEvent = INPUT_BROKER_NONE;
-    e.source = this->originName;
-
+    InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0};
     static uint32_t lastPressed = millis();
     if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
         if (lastPressed + 200 < millis()) {

From 2354c52b16dc13c0f44d7dd456c3adf33cb78840 Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 20:53:49 +1000
Subject: [PATCH 77/81] Only log good times. (It's not always a good time then)
 (#7904)

Further to https://github.com/meshtastic/firmware/pull/7897 ,
there was another log line that was triggering indiscriminantly on
GPS_INTERVAL_THRESHOLD .

Rather than logging a bad time 4000 times, let's just log one good time
when it is set.
---
 src/gps/GPS.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index b2904f2de..7a253ff50 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1546,10 +1546,9 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
         t.tm_year = d.year() - 1900;
         t.tm_isdst = false;
         if (t.tm_mon > -1) {
-            LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
-                      t.tm_sec, ti.age());
             if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) {
-                LOG_DEBUG("Time set.");
+                LOG_DEBUG("NMEA GPS time set %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour,
+                          t.tm_min, t.tm_sec, ti.age());
                 return true;
             } else {
                 return false;

From 15f4aebcd5439db9c6c50539590fce6a886fba1e Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 20:54:08 +1000
Subject: [PATCH 78/81] 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.
---
 variants/esp32s3/rak_wismesh_tap_v2/platformio.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini
index 8b86e0217..de4714efa 100644
--- a/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini
+++ b/variants/esp32s3/rak_wismesh_tap_v2/platformio.ini
@@ -70,6 +70,7 @@ build_flags =
   ${ft5x06.build_flags}
   -D LGFX_SCREEN_WIDTH=240
   -D LGFX_SCREEN_HEIGHT=320
+  -D DISPLAY_SIZE=320x240 ; landscape mode
   -D LGFX_PANEL=ST7789
   -D LGFX_ROTATION=1
   -D LGFX_TOUCH_X_MIN=0

From 209157c9dd72e5832bbb4482a51be3ee1801c56e Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 8 Sep 2025 05:55:44 -0500
Subject: [PATCH 79/81] chore(deps): update meshtastic/device-ui digest to
 233d18e (#7890)

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 c58b14db1..16bb0eb96 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -118,7 +118,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/a04bc94b45dacdabf3ae1832d4591390e35fc61f.zip
+	https://github.com/meshtastic/device-ui/archive/233d18ef42e9d189f90fdfe621f0cd7edff2d221.zip
 
 ; Common libs for environmental measurements in telemetry module
 [environmental_base]

From c5b95f5a4b68377350bfdd809bdedbe5d942646e Mon Sep 17 00:00:00 2001
From: Tom Fifield 
Date: Mon, 8 Sep 2025 20:56:19 +1000
Subject: [PATCH 80/81] Disable web server on Picomputer (#7907)

Meshtastic no longer fits on the flash of the Picomputer.

Since this is a handheld, portable device, it's unlikely that people are
connecting to it via the webserver. So, disable the webserver and it fits
again:

```
Checking size .pio/build/picomputer-s3-tft/firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [===       ]  32.4% (used 106056 bytes from 327680 bytes)
Flash: [==========]  99.1% (used 3313913 bytes from 3342336 bytes)
```

Fixes: https://github.com/meshtastic/firmware/issues/7906
---
 variants/esp32s3/picomputer-s3/platformio.ini | 1 +
 1 file changed, 1 insertion(+)

diff --git a/variants/esp32s3/picomputer-s3/platformio.ini b/variants/esp32s3/picomputer-s3/platformio.ini
index d5847959b..cd67f86b3 100644
--- a/variants/esp32s3/picomputer-s3/platformio.ini
+++ b/variants/esp32s3/picomputer-s3/platformio.ini
@@ -26,6 +26,7 @@ extends = env:picomputer-s3
 
 build_flags =
   ${env:picomputer-s3.build_flags}
+  -D MESHTASTIC_EXCLUDE_WEBSERVER=1
   -D INPUTDRIVER_MATRIX_TYPE=1
   -D USE_PIN_BUZZER=PIN_BUZZER
   -D USE_SX127x

From 39ff88050663f64e588eab95f2307f8d95517a45 Mon Sep 17 00:00:00 2001
From: Manuel <71137295+mverch67@users.noreply.github.com>
Date: Mon, 8 Sep 2025 12:56:47 +0200
Subject: [PATCH 81/81] reorganize 8MB partition for MUI devices (#7860)

* reorganize 8MB partition for MUI devices

* update device-install scripts to MUI 8MB partition scheme
---
 bin/device-install.bat                        | 32 +++++++---
 bin/device-install.sh                         | 63 +++++++++++--------
 boards/seeed-sensecap-indicator.json          |  2 +-
 boards/unphone.json                           |  2 +-
 partition-table-8MB.csv                       |  7 +++
 variants/esp32s3/picomputer-s3/platformio.ini |  2 +-
 .../seeed-sensecap-indicator/platformio.ini   |  2 +-
 variants/esp32s3/unphone/platformio.ini       |  4 +-
 variants/esp32s3/unphone/variant.h            |  1 -
 9 files changed, 75 insertions(+), 40 deletions(-)
 create mode 100644 partition-table-8MB.csv

diff --git a/bin/device-install.bat b/bin/device-install.bat
index 56de4dc10..9c206d718 100755
--- a/bin/device-install.bat
+++ b/bin/device-install.bat
@@ -7,6 +7,7 @@ SET "DEBUG=0"
 SET "PYTHON="
 SET "TFT_BUILD=0"
 SET "BIGDB8=0"
+SET "MUIDB8=0"
 SET "BIGDB16=0"
 SET "ESPTOOL_BAUD=115200"
 SET "ESPTOOL_CMD="
@@ -17,7 +18,8 @@ SET "BPS_RESET=0"
 SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv"
 SET "C3=esp32c3"
 @REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
-SET "BIGDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
+SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
+SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
 SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv"
 
 GOTO getopts
@@ -162,6 +164,15 @@ FOR %%a IN (%BIGDB_8MB%) DO (
 )
 :end_loop_bigdb_8mb
 
+FOR %%a IN (%MUIDB_8MB%) DO (
+    IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
+        @REM We are working with any of %MUIDB_8MB%.
+        SET "MUIDB8=1"
+        GOTO end_loop_muidb_8mb
+    )
+)
+:end_loop_muidb_8mb
+
 FOR %%a IN (%BIGDB_16MB%) DO (
     IF NOT "!FILENAME:%%a=!"=="!FILENAME!" (
         @REM We are working with any of %BIGDB_16MB%.
@@ -172,6 +183,7 @@ FOR %%a IN (%BIGDB_16MB%) DO (
 :end_loop_bigdb_16mb
 
 IF %BIGDB8% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 8mb partition selected."
+IF %MUIDB8% EQU 1 CALL :LOG_MESSAGE INFO "MUIDB 8mb partition selected."
 IF %BIGDB16% EQU 1 CALL :LOG_MESSAGE INFO "BigDB 16mb partition selected."
 
 @REM Extract BASENAME from %FILENAME% for later use.
@@ -216,6 +228,12 @@ IF %BIGDB8% EQU 1 (
     SET "SPIFFS_OFFSET=0x670000"
 )
 
+@REM Offsets for MUIDB 8mb.
+IF %MUIDB8% EQU 1 (
+    SET "OTA_OFFSET=0x5D0000"
+    SET "SPIFFS_OFFSET=0x670000"
+)
+
 @REM Offsets for BigDB 16mb.
 IF %BIGDB16% EQU 1 (
     SET "OTA_OFFSET=0x650000"
@@ -232,14 +250,14 @@ IF NOT EXIST !SPIFFS_FILENAME! CALL :LOG_MESSAGE ERROR "File does not exist: "!S
 
 @REM Flashing operations.
 CALL :LOG_MESSAGE INFO "Trying to flash "!FILENAME!", but first erasing and writing system information..."
-CALL :RUN_ESPTOOL !ESPTOOL_BAUD! erase-flash || GOTO eof
-CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash 0x00 "!FILENAME!" || GOTO eof
+CALL :RUN_ESPTOOL !ESPTOOL_BAUD! erase_flash || GOTO eof
+CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash 0x00 "!FILENAME!" || GOTO eof
 
 CALL :LOG_MESSAGE INFO "Trying to flash BLEOTA "!OTA_FILENAME!" at OTA_OFFSET !OTA_OFFSET!..."
-CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !OTA_OFFSET! "!OTA_FILENAME!" || GOTO eof
+CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !OTA_OFFSET! "!OTA_FILENAME!" || GOTO eof
 
 CALL :LOG_MESSAGE INFO "Trying to flash SPIFFS "!SPIFFS_FILENAME!" at SPIFFS_OFFSET !SPIFFS_OFFSET!..."
-CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write-flash !SPIFFS_OFFSET! "!SPIFFS_FILENAME!" || GOTO eof
+CALL :RUN_ESPTOOL !ESPTOOL_BAUD! write_flash !SPIFFS_OFFSET! "!SPIFFS_FILENAME!" || GOTO eof
 
 CALL :LOG_MESSAGE INFO "Script complete!."
 
@@ -251,9 +269,9 @@ EXIT /B %ERRORLEVEL%
 :RUN_ESPTOOL
 @REM Subroutine used to run ESPTOOL_CMD with arguments.
 @REM Also handles %ERRORLEVEL%.
-@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write-flash] [OFFSET] [Filename]
+@REM CALL :RUN_ESPTOOL [Baud] [erase_flash|write_flash] [OFFSET] [Filename]
 @REM.
-@REM Example:: CALL :RUN_ESPTOOL 115200 write-flash 0x10000 "firmwarefile.bin"
+@REM Example:: CALL :RUN_ESPTOOL 115200 write_flash 0x10000 "firmwarefile.bin"
 IF %DEBUG% EQU 1 CALL :LOG_MESSAGE DEBUG "About to run command: !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4"
 CALL :RESET_ERROR
 !ESPTOOL_CMD! --baud %~1 %~2 %~3 %~4
diff --git a/bin/device-install.sh b/bin/device-install.sh
index 98937f29a..594f9dd6b 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -11,31 +11,33 @@ FIRMWARE_OFFSET=0x00
 
 # Variant groups
 BIGDB_8MB=(
-	"picomputer-s3"
-	"unphone"
-	"seeed-sensecap-indicator"
-	"crowpanel-esp32s3"
-	"heltec_capsule_sensor_v3"
-	"heltec-v3"
-	"heltec-vision-master-e213"
-	"heltec-vision-master-e290"
-	"heltec-vision-master-t190"
-	"heltec-wireless-paper"
-	"heltec-wireless-tracker"
-	"heltec-wsl-v3"
-	"icarus"
-	"seeed-xiao-s3"
-	"tbeam-s3-core"
-	"tracksenger"
+    "crowpanel-esp32s3"
+    "heltec_capsule_sensor_v3"
+    "heltec-v3"
+    "heltec-vision-master-e213"
+    "heltec-vision-master-e290"
+    "heltec-vision-master-t190"
+    "heltec-wireless-paper"
+    "heltec-wireless-tracker"
+    "heltec-wsl-v3"
+    "icarus"
+    "seeed-xiao-s3"
+    "tbeam-s3-core"
+    "tracksenger"
+)
+MUIDB_8MB=(
+    "picomputer-s3"
+    "unphone"
+    "seeed-sensecap-indicator"
 )
 BIGDB_16MB=(
-	"t-deck"
-	"mesh-tab"
-	"t-energy-s3"
-	"dreamcatcher"
-	"ESP32-S3-Pico"
-	"m5stack-cores3"
-	"station-g2"
+    "t-deck"
+    "mesh-tab"
+    "t-energy-s3"
+    "dreamcatcher"
+    "ESP32-S3-Pico"
+    "m5stack-cores3"
+    "station-g2"
     "t-eth-elite"
     "tlora-pager"
     "t-watch-s3"
@@ -110,8 +112,8 @@ while [ $# -gt 0 ]; do
         shift
         ;;
     --1200bps-reset)
-		    BPS_RESET=true
-		    ;;
+        BPS_RESET=true
+        ;;
     --) # Stop parsing options
         shift
         break
@@ -162,6 +164,13 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
         fi
     done
 
+    for variant in "${MUIDB_8MB[@]}"; do
+        if [ -z "${FILENAME##*"$variant"*}" ]; then
+            OFFSET=0x670000
+            OTA_OFFSET=0x5D0000
+        fi
+    done
+
     # littlefs* offset for BigDB 16mb and OTA OFFSET.
     for variant in "${BIGDB_16MB[@]}"; do
         if [ -z "${FILENAME##*"$variant"*}" ]; then
@@ -208,9 +217,9 @@ if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
     $ESPTOOL_CMD erase-flash
     $ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
     echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
-    $ESPTOOL_CMD write-flash $OTA_OFFSET "${OTAFILE}"
+    $ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
     echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
-    $ESPTOOL_CMD write-flash $OFFSET "${SPIFFSFILE}"
+    $ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
 
 else
     show_help
diff --git a/boards/seeed-sensecap-indicator.json b/boards/seeed-sensecap-indicator.json
index 03bff35b5..37a97cdf1 100644
--- a/boards/seeed-sensecap-indicator.json
+++ b/boards/seeed-sensecap-indicator.json
@@ -2,7 +2,7 @@
   "build": {
     "arduino": {
       "ldscript": "esp32s3_out.ld",
-      "partitions": "default_8MB.csv",
+      "partitions": "partition-table-8MB.csv",
       "memory_type": "qio_opi"
     },
     "core": "esp32",
diff --git a/boards/unphone.json b/boards/unphone.json
index bf711993c..4d37f7bb5 100644
--- a/boards/unphone.json
+++ b/boards/unphone.json
@@ -3,7 +3,7 @@
     "arduino": {
       "ldscript": "esp32s3_out.ld",
       "memory_type": "qio_opi",
-      "partitions": "default_8MB.csv"
+      "partitions": "partition-table-8MB.csv"
     },
     "core": "esp32",
     "extra_flags": [
diff --git a/partition-table-8MB.csv b/partition-table-8MB.csv
new file mode 100644
index 000000000..0bfbc22ba
--- /dev/null
+++ b/partition-table-8MB.csv
@@ -0,0 +1,7 @@
+# This is a layout for 8MB of flash for MUI devices
+# Name,   Type, SubType, Offset,  Size, Flags
+nvs,      data, nvs,     0x9000,  0x5000,
+otadata,  data, ota,     0xe000,  0x2000,
+app0,     app,  ota_0,   0x10000, 0x5C0000,
+flashApp, app,  ota_1,   0x5D0000,0x0A0000,
+spiffs,   data, spiffs,  0x670000,0x180000
\ No newline at end of file
diff --git a/variants/esp32s3/picomputer-s3/platformio.ini b/variants/esp32s3/picomputer-s3/platformio.ini
index d5847959b..b47d5733f 100644
--- a/variants/esp32s3/picomputer-s3/platformio.ini
+++ b/variants/esp32s3/picomputer-s3/platformio.ini
@@ -2,7 +2,7 @@
 extends = esp32s3_base
 board = bpi_picow_esp32_s3
 board_check = true
-board_build.partitions = default_8MB.csv
+board_build.partitions = partition-table-8MB.csv
 ;OpenOCD flash method
 ;upload_protocol = esp-builtin
 ;Normal method
diff --git a/variants/esp32s3/seeed-sensecap-indicator/platformio.ini b/variants/esp32s3/seeed-sensecap-indicator/platformio.ini
index f408054cf..25ec3ebfc 100644
--- a/variants/esp32s3/seeed-sensecap-indicator/platformio.ini
+++ b/variants/esp32s3/seeed-sensecap-indicator/platformio.ini
@@ -6,7 +6,7 @@ platform_packages =
 
 board = seeed-sensecap-indicator
 board_check = true
-board_build.partitions = default_8MB.csv
+board_build.partitions = partition-table-8MB.csv
 upload_protocol = esptool
 
 build_flags = ${esp32_base.build_flags}
diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini
index 476858ff5..f17a27e17 100644
--- a/variants/esp32s3/unphone/platformio.ini
+++ b/variants/esp32s3/unphone/platformio.ini
@@ -3,7 +3,7 @@
 [env:unphone]
 extends = esp32s3_base
 board = unphone
-board_build.partitions = default_8MB.csv
+board_build.partitions = partition-table-8MB.csv
 upload_speed = 921600
 monitor_speed = 115200
 monitor_filters = esp32_exception_decoder
@@ -20,6 +20,7 @@ build_flags =
   -D UNPHONE_LORA=0
   -D UNPHONE_FACTORY_MODE=0
   -D USE_SX127x
+  -D SDCARD_CS=43
 
 build_src_filter =
   ${esp32s3_base.build_src_filter}
@@ -41,6 +42,7 @@ build_flags =
   -D HAS_SCREEN=1
   -D HAS_TFT=1
   -D HAS_SDCARD
+  -D SDCARD_CS=43
   -D DISPLAY_SET_RESOLUTION
   -D RAM_SIZE=6144
   -D LV_CACHE_DEF_SIZE=2097152
diff --git a/variants/esp32s3/unphone/variant.h b/variants/esp32s3/unphone/variant.h
index e186b5740..366b49233 100644
--- a/variants/esp32s3/unphone/variant.h
+++ b/variants/esp32s3/unphone/variant.h
@@ -52,7 +52,6 @@
 #undef GPS_TX_PIN
 
 #define SD_SPI_FREQUENCY 25000000
-#define SDCARD_CS 43
 
 #define LED_PIN 13     // the red part of the RGB LED
 #define LED_STATE_ON 0 // State when LED is lit